- Revision
- 201436
- Author
- [email protected]
- Date
- 2016-05-26 15:30:05 -0700 (Thu, 26 May 2016)
Log Message
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.
Modified Paths
Diff
Modified: trunk/Source/_javascript_Core/ChangeLog (201435 => 201436)
--- trunk/Source/_javascript_Core/ChangeLog 2016-05-26 22:05:26 UTC (rev 201435)
+++ trunk/Source/_javascript_Core/ChangeLog 2016-05-26 22:30:05 UTC (rev 201436)
@@ -1,3 +1,49 @@
+2016-05-26 Geoffrey Garen <[email protected]>
+
+ 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 <[email protected]>
replaceable own properties seem to ignore replacement after property caching
Modified: trunk/Source/_javascript_Core/bytecode/ObjectPropertyConditionSet.cpp (201435 => 201436)
--- trunk/Source/_javascript_Core/bytecode/ObjectPropertyConditionSet.cpp 2016-05-26 22:05:26 UTC (rev 201435)
+++ trunk/Source/_javascript_Core/bytecode/ObjectPropertyConditionSet.cpp 2016-05-26 22:30:05 UTC (rev 201436)
@@ -263,10 +263,13 @@
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);
Modified: trunk/Source/_javascript_Core/interpreter/Interpreter.cpp (201435 => 201436)
--- trunk/Source/_javascript_Core/interpreter/Interpreter.cpp 2016-05-26 22:05:26 UTC (rev 201435)
+++ trunk/Source/_javascript_Core/interpreter/Interpreter.cpp 2016-05-26 22:30:05 UTC (rev 201436)
@@ -938,6 +938,9 @@
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 @@
}
}
+ if (variableObject->structure()->isUncacheableDictionary())
+ variableObject->flattenDictionaryObject(vm);
+
if (numVariables || numFunctions) {
BatchedTransitionOptimizer optimizer(vm, variableObject);
if (variableObject->next())
@@ -1243,6 +1249,9 @@
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`.
Modified: trunk/Source/_javascript_Core/runtime/BatchedTransitionOptimizer.h (201435 => 201436)
--- trunk/Source/_javascript_Core/runtime/BatchedTransitionOptimizer.h 2016-05-26 22:05:26 UTC (rev 201435)
+++ trunk/Source/_javascript_Core/runtime/BatchedTransitionOptimizer.h 2016-05-26 22:30:05 UTC (rev 201436)
@@ -35,22 +35,10 @@
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
Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp (201435 => 201436)
--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp 2016-05-26 22:05:26 UTC (rev 201435)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp 2016-05-26 22:30:05 UTC (rev 201436)
@@ -319,6 +319,8 @@
{
ASSERT(vm.currentThreadIsHoldingAPILock());
+ Base::setStructure(vm, Structure::toCacheableDictionaryTransition(vm, structure()));
+
JSGlobalObject::globalExec()->init(0, 0, CallFrame::noCaller(), 0, 0);
m_debugger = 0;
Modified: trunk/Source/_javascript_Core/runtime/Operations.cpp (201435 => 201436)
--- trunk/Source/_javascript_Core/runtime/Operations.cpp 2016-05-26 22:05:26 UTC (rev 201435)
+++ trunk/Source/_javascript_Core/runtime/Operations.cpp 2016-05-26 22:30:05 UTC (rev 201436)
@@ -120,4 +120,27 @@
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
Modified: trunk/Source/_javascript_Core/runtime/Operations.h (201435 => 201436)
--- trunk/Source/_javascript_Core/runtime/Operations.h 2016-05-26 22:05:26 UTC (rev 201435)
+++ trunk/Source/_javascript_Core/runtime/Operations.h 2016-05-26 22:30:05 UTC (rev 201436)
@@ -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 @@
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