Title: [221853] trunk
Revision
221853
Author
[email protected]
Date
2017-09-11 01:10:06 -0700 (Mon, 11 Sep 2017)

Log Message

[JSC] Optimize Object.keys by using careful array allocation
https://bugs.webkit.org/show_bug.cgi?id=176654

Reviewed by Darin Adler.

JSTests:

* microbenchmarks/object-keys.js: Added.
(test):

Source/_javascript_Core:

SixSpeed object-assign.es6 stresses Object.keys. Object.keys is one of frequently used
function in JS apps. Luckily Object.keys has several good features.

1. Once PropertyNameArray is allocated, we know the length of the result array since
we do not need to filter out keys listed in PropertyNameArray. The execption is ProxyObject,
but it rarely appears. ProxyObject case goes to the generic path.

2. Object.keys does not need to access object after listing PropertyNameArray. It means
that we do not need to worry about enumeration attribute change by touching object.

This patch adds a fast path for Object.keys's array allocation. We allocate the JSArray
with the size and ArrayContiguous indexing shape.

This further improves SixSpeed object-assign.es5 by 13%.

                                    baseline                  patched
Microbenchmarks:
   object-keys-map-values       73.4324+-2.5397     ^     62.5933+-2.6677        ^ definitely 1.1732x faster
   object-keys                  40.8828+-1.5851     ^     29.2066+-1.8944        ^ definitely 1.3998x faster

                                    baseline                  patched
SixSpeed:
   object-assign.es5           384.8719+-10.7204    ^    340.2734+-12.0947       ^ definitely 1.1311x faster

BTW, the further optimization of Object.keys can be considered: introducing own property keys
cache which is similar to the current enumeration cache. But this patch is orthogonal to
this optimization!

* runtime/ObjectConstructor.cpp:
(JSC::objectConstructorValues):
(JSC::ownPropertyKeys):
* runtime/ObjectConstructor.h:

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (221852 => 221853)


--- trunk/JSTests/ChangeLog	2017-09-11 07:19:39 UTC (rev 221852)
+++ trunk/JSTests/ChangeLog	2017-09-11 08:10:06 UTC (rev 221853)
@@ -1,3 +1,13 @@
+2017-09-09  Yusuke Suzuki  <[email protected]>
+
+        [JSC] Optimize Object.keys by using careful array allocation
+        https://bugs.webkit.org/show_bug.cgi?id=176654
+
+        Reviewed by Darin Adler.
+
+        * microbenchmarks/object-keys.js: Added.
+        (test):
+
 2017-09-09  Filip Pizlo  <[email protected]>
 
         Error should compute .stack and friends lazily

Added: trunk/JSTests/microbenchmarks/object-keys.js (0 => 221853)


--- trunk/JSTests/microbenchmarks/object-keys.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/object-keys.js	2017-09-11 08:10:06 UTC (rev 221853)
@@ -0,0 +1,13 @@
+var object = {};
+for (var i = 0; i < 1e3; ++i) {
+    object[i + 'prop'] = i;
+}
+
+function test(object)
+{
+    return Object.keys(object);
+}
+noInline(test);
+
+for (var i = 0; i < 1e3; ++i)
+    test(object);

Modified: trunk/Source/_javascript_Core/ChangeLog (221852 => 221853)


--- trunk/Source/_javascript_Core/ChangeLog	2017-09-11 07:19:39 UTC (rev 221852)
+++ trunk/Source/_javascript_Core/ChangeLog	2017-09-11 08:10:06 UTC (rev 221853)
@@ -1,3 +1,43 @@
+2017-09-09  Yusuke Suzuki  <[email protected]>
+
+        [JSC] Optimize Object.keys by using careful array allocation
+        https://bugs.webkit.org/show_bug.cgi?id=176654
+
+        Reviewed by Darin Adler.
+
+        SixSpeed object-assign.es6 stresses Object.keys. Object.keys is one of frequently used
+        function in JS apps. Luckily Object.keys has several good features.
+
+        1. Once PropertyNameArray is allocated, we know the length of the result array since
+        we do not need to filter out keys listed in PropertyNameArray. The execption is ProxyObject,
+        but it rarely appears. ProxyObject case goes to the generic path.
+
+        2. Object.keys does not need to access object after listing PropertyNameArray. It means
+        that we do not need to worry about enumeration attribute change by touching object.
+
+        This patch adds a fast path for Object.keys's array allocation. We allocate the JSArray
+        with the size and ArrayContiguous indexing shape.
+
+        This further improves SixSpeed object-assign.es5 by 13%.
+
+                                            baseline                  patched
+        Microbenchmarks:
+           object-keys-map-values       73.4324+-2.5397     ^     62.5933+-2.6677        ^ definitely 1.1732x faster
+           object-keys                  40.8828+-1.5851     ^     29.2066+-1.8944        ^ definitely 1.3998x faster
+
+                                            baseline                  patched
+        SixSpeed:
+           object-assign.es5           384.8719+-10.7204    ^    340.2734+-12.0947       ^ definitely 1.1311x faster
+
+        BTW, the further optimization of Object.keys can be considered: introducing own property keys
+        cache which is similar to the current enumeration cache. But this patch is orthogonal to
+        this optimization!
+
+        * runtime/ObjectConstructor.cpp:
+        (JSC::objectConstructorValues):
+        (JSC::ownPropertyKeys):
+        * runtime/ObjectConstructor.h:
+
 2017-09-10  Mark Lam  <[email protected]>
 
         Fix all ExceptionScope verification failures in _javascript_Core.

Modified: trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp (221852 => 221853)


--- trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp	2017-09-11 07:19:39 UTC (rev 221852)
+++ trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp	2017-09-11 08:10:06 UTC (rev 221853)
@@ -379,7 +379,7 @@
     JSObject* target = targetValue.toObject(exec);
     RETURN_IF_EXCEPTION(scope, { });
 
-    JSArray* values = constructEmptyArray(exec, 0);
+    JSArray* values = constructEmptyArray(exec, nullptr);
     RETURN_IF_EXCEPTION(scope, { });
 
     PropertyNameArray properties(exec, PropertyNameMode::Strings);
@@ -386,6 +386,7 @@
     target->methodTable(vm)->getOwnPropertyNames(target, exec, properties, EnumerationMode(DontEnumPropertiesMode::Include));
     RETURN_IF_EXCEPTION(scope, { });
 
+    unsigned index = 0;
     auto addValue = [&] (PropertyName propertyName) {
         PropertySlot slot(target, PropertySlot::InternalMethodType::GetOwnProperty);
         bool hasProperty = target->methodTable(vm)->getOwnPropertySlot(target, exec, propertyName, slot);
@@ -402,7 +403,7 @@
             value = target->get(exec, propertyName);
         RETURN_IF_EXCEPTION(scope, void());
 
-        values->push(exec, value);
+        values->putDirectIndex(exec, index++, value);
     };
 
     for (unsigned i = 0, numProperties = properties.size(); i < numProperties; i++) {
@@ -839,9 +840,6 @@
     object->methodTable(vm)->getOwnPropertyNames(object, exec, properties, EnumerationMode(dontEnumPropertiesMode));
     RETURN_IF_EXCEPTION(scope, nullptr);
 
-    JSArray* keys = constructEmptyArray(exec, 0);
-    RETURN_IF_EXCEPTION(scope, nullptr);
-
     // https://tc39.github.io/ecma262/#sec-enumerableownproperties
     // If {object} is a Proxy, an explicit and observable [[GetOwnProperty]] op is required to filter out non-enumerable properties.
     // In other cases, filtering has already been performed.
@@ -854,6 +852,28 @@
         return object->getOwnPropertyDescriptor(exec, name, descriptor) && descriptor.enumerable();
     };
 
+    // If !mustFilterProperty and PropertyNameMode::Strings mode, we do not need to filter out any entries in PropertyNameArray.
+    // We can use fast allocation and initialization.
+    if (!mustFilterProperty && propertyNameMode == PropertyNameMode::Strings && properties.size() < MIN_SPARSE_ARRAY_INDEX) {
+        size_t numProperties = properties.size();
+        JSArray* keys = JSArray::create(vm, exec->lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), numProperties);
+        WriteBarrier<Unknown>* buffer = keys->butterfly()->contiguous().data();
+        for (size_t i = 0; i < numProperties; i++) {
+            const auto& identifier = properties[i];
+            ASSERT(!identifier.isSymbol());
+            buffer[i].set(vm, keys, jsOwnedString(&vm, identifier.string()));
+        }
+        return keys;
+    }
+
+    JSArray* keys = constructEmptyArray(exec, nullptr);
+    RETURN_IF_EXCEPTION(scope, nullptr);
+
+    unsigned index = 0;
+    auto pushDirect = [&] (ExecState* exec, JSArray* array, JSValue value) {
+        array->putDirectIndex(exec, index++, value);
+    };
+
     switch (propertyNameMode) {
     case PropertyNameMode::Strings: {
         size_t numProperties = properties.size();
@@ -863,7 +883,7 @@
             bool hasProperty = filterPropertyIfNeeded(identifier);
             EXCEPTION_ASSERT(!scope.exception() || !hasProperty);
             if (hasProperty)
-                keys->push(exec, jsOwnedString(exec, identifier.string()));
+                pushDirect(exec, keys, jsOwnedString(exec, identifier.string()));
             RETURN_IF_EXCEPTION(scope, nullptr);
         }
         break;
@@ -878,7 +898,7 @@
                 bool hasProperty = filterPropertyIfNeeded(identifier);
                 EXCEPTION_ASSERT(!scope.exception() || !hasProperty);
                 if (hasProperty)
-                    keys->push(exec, Symbol::create(vm, static_cast<SymbolImpl&>(*identifier.impl())));
+                    pushDirect(exec, keys, Symbol::create(vm, static_cast<SymbolImpl&>(*identifier.impl())));
                 RETURN_IF_EXCEPTION(scope, nullptr);
             }
         }
@@ -896,7 +916,7 @@
                 bool hasProperty = filterPropertyIfNeeded(identifier);
                 EXCEPTION_ASSERT(!scope.exception() || !hasProperty);
                 if (hasProperty)
-                    keys->push(exec, jsOwnedString(exec, identifier.string()));
+                    pushDirect(exec, keys, jsOwnedString(exec, identifier.string()));
                 RETURN_IF_EXCEPTION(scope, nullptr);
             }
         }
@@ -906,7 +926,7 @@
             bool hasProperty = filterPropertyIfNeeded(identifier);
             EXCEPTION_ASSERT(!scope.exception() || !hasProperty);
             if (hasProperty)
-                keys->push(exec, Symbol::create(vm, static_cast<SymbolImpl&>(*identifier.impl())));
+                pushDirect(exec, keys, Symbol::create(vm, static_cast<SymbolImpl&>(*identifier.impl())));
             RETURN_IF_EXCEPTION(scope, nullptr);
         }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to