Skip to content
Permalink
Browse files
REGRESSION: JSBench spends a lot of time transitioning to/from dictio…
…nary

https://bugs.webkit.org/show_bug.cgi?id=158045

Reviewed by Saam Barati.

15% speedup on jsbench-amazon-firefox, possibly 5% speedup overall on jsbench.

This regression seems to have two parts:

(1) Transitioning the window object to/from dictionary is more expensive
than it used to be to because the window object has lots more properties.
The window object has more properties because, for WebIDL compatibility,
we reify DOM APIs as properties when you delete.

(2) DOM prototypes transition to/from dictionary upon creation
because, once again for WebIDL compatibility, we reify their static
APIs eagerly.

The solution is to chill out a bit on dictionary transitions.

* bytecode/ObjectPropertyConditionSet.cpp: Don't flatten a dictionary
if we've already done so before. This avoids pathological churn, and it
is our idiom in other places.

* interpreter/Interpreter.cpp:
(JSC::Interpreter::execute): Do flatten the global object unconditionally
if it is an uncacheable dictionary because the global object is super
important.

* runtime/BatchedTransitionOptimizer.h:
(JSC::BatchedTransitionOptimizer::BatchedTransitionOptimizer):
(JSC::BatchedTransitionOptimizer::~BatchedTransitionOptimizer): Deleted.
Don't transition away from dictionary after a batched set of property
puts because normal dictionaries are cacheable and that's a perfectly
fine state to be in -- and the transition is expensive.

* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init): Do start the global object out as a cacheable
dictionary because it will inevitably have enough properties to become
a dictionary.

* runtime/Operations.h:
(JSC::normalizePrototypeChain): Same as ObjectPropertyConditionSet.cpp.


Canonical link: https://commits.webkit.org/176239@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@201436 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
geoffreygaren committed May 26, 2016
1 parent 76dc7f8 commit 4c9332da44d255688a5a8c02a09d8a83d24b792a
@@ -1,3 +1,49 @@
2016-05-26 Geoffrey Garen <ggaren@apple.com>

REGRESSION: JSBench spends a lot of time transitioning to/from dictionary
https://bugs.webkit.org/show_bug.cgi?id=158045

Reviewed by Saam Barati.

15% speedup on jsbench-amazon-firefox, possibly 5% speedup overall on jsbench.

This regression seems to have two parts:

(1) Transitioning the window object to/from dictionary is more expensive
than it used to be to because the window object has lots more properties.
The window object has more properties because, for WebIDL compatibility,
we reify DOM APIs as properties when you delete.

(2) DOM prototypes transition to/from dictionary upon creation
because, once again for WebIDL compatibility, we reify their static
APIs eagerly.

The solution is to chill out a bit on dictionary transitions.

* bytecode/ObjectPropertyConditionSet.cpp: Don't flatten a dictionary
if we've already done so before. This avoids pathological churn, and it
is our idiom in other places.

* interpreter/Interpreter.cpp:
(JSC::Interpreter::execute): Do flatten the global object unconditionally
if it is an uncacheable dictionary because the global object is super
important.

* runtime/BatchedTransitionOptimizer.h:
(JSC::BatchedTransitionOptimizer::BatchedTransitionOptimizer):
(JSC::BatchedTransitionOptimizer::~BatchedTransitionOptimizer): Deleted.
Don't transition away from dictionary after a batched set of property
puts because normal dictionaries are cacheable and that's a perfectly
fine state to be in -- and the transition is expensive.

* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init): Do start the global object out as a cacheable
dictionary because it will inevitably have enough properties to become
a dictionary.

* runtime/Operations.h:
(JSC::normalizePrototypeChain): Same as ObjectPropertyConditionSet.cpp.

2016-05-25 Geoffrey Garen <ggaren@apple.com>

replaceable own properties seem to ignore replacement after property caching
@@ -263,10 +263,13 @@ ObjectPropertyConditionSet generateConditions(
JSObject* object = jsCast<JSObject*>(value);
structure = object->structure(vm);

// Since we're accessing a prototype repeatedly, it's a good bet that it should not be
// treated as a dictionary.
if (structure->isDictionary()) {
if (concurrency == MainThread) {
if (structure->hasBeenFlattenedBefore()) {
if (verbose)
dataLog("Dictionary has been flattened before, so invalid.\n");
return ObjectPropertyConditionSet::invalid();
}
if (verbose)
dataLog("Flattening ", pointerDump(structure));
structure->flattenDictionaryStructure(vm, object);
@@ -938,6 +938,9 @@ JSValue Interpreter::execute(ProgramExecutable* program, CallFrame* callFrame, J
if (UNLIKELY(vm.shouldTriggerTermination(callFrame)))
return throwTerminatedExecutionException(callFrame);

if (scope->structure()->isUncacheableDictionary())
scope->flattenDictionaryObject(vm);

ASSERT(codeBlock->numParameters() == 1); // 1 parameter for 'this'.

ProtoCallFrame protoCallFrame;
@@ -1186,6 +1189,9 @@ JSValue Interpreter::execute(EvalExecutable* eval, CallFrame* callFrame, JSValue
}
}

if (variableObject->structure()->isUncacheableDictionary())
variableObject->flattenDictionaryObject(vm);

if (numVariables || numFunctions) {
BatchedTransitionOptimizer optimizer(vm, variableObject);
if (variableObject->next())
@@ -1243,6 +1249,9 @@ JSValue Interpreter::execute(ModuleProgramExecutable* executable, CallFrame* cal
if (UNLIKELY(vm.shouldTriggerTermination(callFrame)))
return throwTerminatedExecutionException(callFrame);

if (scope->structure()->isUncacheableDictionary())
scope->flattenDictionaryObject(vm);

ASSERT(codeBlock->numParameters() == 1); // 1 parameter for 'this'.

// The |this| of the module is always `undefined`.
@@ -35,22 +35,10 @@ class BatchedTransitionOptimizer {
WTF_MAKE_NONCOPYABLE(BatchedTransitionOptimizer);
public:
BatchedTransitionOptimizer(VM& vm, JSObject* object)
: m_vm(&vm)
, m_object(object)
{
if (!m_object->structure(vm)->isDictionary())
m_object->convertToDictionary(vm);
if (!object->structure(vm)->isDictionary())
object->convertToDictionary(vm);
}

~BatchedTransitionOptimizer()
{
if (m_object->structure()->isDictionary())
m_object->flattenDictionaryObject(*m_vm);
}

private:
VM* m_vm;
JSObject* m_object;
};

} // namespace JSC
@@ -319,6 +319,8 @@ void JSGlobalObject::init(VM& vm)
{
ASSERT(vm.currentThreadIsHoldingAPILock());

Base::setStructure(vm, Structure::toCacheableDictionaryTransition(vm, structure()));

JSGlobalObject::globalExec()->init(0, 0, CallFrame::noCaller(), 0, 0);

m_debugger = 0;
@@ -120,4 +120,27 @@ bool jsIsFunctionType(JSValue v)
return false;
}

size_t normalizePrototypeChain(CallFrame* callFrame, Structure* structure)
{
VM& vm = callFrame->vm();
size_t count = 0;
while (1) {
if (structure->isProxy())
return InvalidPrototypeChain;
JSValue v = structure->prototypeForLookup(callFrame);
if (v.isNull())
return count;

JSCell* base = v.asCell();
structure = base->structure(vm);
if (structure->isDictionary()) {
if (structure->hasBeenFlattenedBefore())
return InvalidPrototypeChain;
structure->flattenDictionaryStructure(vm, asObject(base));
}

++count;
}
}

} // namespace JSC
@@ -28,11 +28,14 @@

namespace JSC {

#define InvalidPrototypeChain (std::numeric_limits<size_t>::max())

NEVER_INLINE JSValue jsAddSlowCase(CallFrame*, JSValue, JSValue);
JSValue jsTypeStringForValue(CallFrame*, JSValue);
JSValue jsTypeStringForValue(VM&, JSGlobalObject*, JSValue);
bool jsIsObjectTypeOrNull(CallFrame*, JSValue);
bool jsIsFunctionType(JSValue);
size_t normalizePrototypeChain(CallFrame*, Structure*);

ALWAYS_INLINE JSValue jsString(ExecState* exec, JSString* s1, JSString* s2)
{
@@ -192,30 +195,6 @@ ALWAYS_INLINE JSValue jsAdd(CallFrame* callFrame, JSValue v1, JSValue v2)
return jsAddSlowCase(callFrame, v1, v2);
}

#define InvalidPrototypeChain (std::numeric_limits<size_t>::max())

inline size_t normalizePrototypeChain(CallFrame* callFrame, Structure* structure)
{
VM& vm = callFrame->vm();
size_t count = 0;
while (1) {
if (structure->isProxy())
return InvalidPrototypeChain;
JSValue v = structure->prototypeForLookup(callFrame);
if (v.isNull())
return count;

JSCell* base = v.asCell();
structure = base->structure(vm);
// Since we're accessing a prototype in a loop, it's a good bet that it
// should not be treated as a dictionary.
if (structure->isDictionary())
structure->flattenDictionaryStructure(vm, asObject(base));

++count;
}
}

} // namespace JSC

#endif // Operations_h

0 comments on commit 4c9332d

Please sign in to comment.