Skip to content

Commit

Permalink
[JSC] Add fast path for Object.assign(target, NamedProperties + Index…
Browse files Browse the repository at this point in the history
…edProperties)

https://bugs.webkit.org/show_bug.cgi?id=261219
rdar://115070621

Reviewed by Yusuke Suzuki.

Previously, we have fast path only for Object.assign(target, NamedProperties).
This patch extends the Object.assign fast path for source consists of
NamedProperties and IndexedProperties.

                                                   without                     with

object-assign-slow-put-array-storage-source     9.3211+-0.0820     ^      4.7620+-0.0325        ^ definitely 1.9574x faster
object-assign-contiguous-source                 6.5756+-0.2071     ^      2.4984+-0.0273        ^ definitely 2.6320x faster
object-assign-int32-source                      6.4450+-0.0493     ^      2.6511+-0.0796        ^ definitely 2.4310x faster
object-assign-multiple-sources                 22.1794+-0.1457     ^     10.5955+-0.2918        ^ definitely 2.0933x faster
object-assign-array-storage-source              9.2886+-0.0758     ^      4.7451+-0.2034        ^ definitely 1.9575x faster
object-assign-double-source                     6.5618+-0.1262     ^      2.8638+-0.0560        ^ definitely 2.2913x faster
object-assign-empty-target-sp3-charts-chartjs   2.8745+-0.0313     ^      1.8253+-0.0134        ^ definitely 1.5748x faster

* JSTests/microbenchmarks/object-assign-empty-target-array-storage-source.js: Added.
* JSTests/microbenchmarks/object-assign-empty-target-contiguous-source.js: Added.
* JSTests/microbenchmarks/object-assign-empty-target-double-source.js: Added.
* JSTests/microbenchmarks/object-assign-empty-target-int32-source.js: Added.
* JSTests/microbenchmarks/object-assign-empty-target-slow-put-array-storage-source.js: Added.
* JSTests/microbenchmarks/object-assign-empty-target-sp3-charts.js: Added.
* JSTests/stress/object-assign-empty-indexed-property-source.js: Added.
(shouldBe):
(test):
* Source/JavaScriptCore/dfg/DFGOperations.cpp:
(JSC::DFG::JSC_DEFINE_JIT_OPERATION):
* Source/JavaScriptCore/runtime/JSObject.cpp:
(JSC::JSObject::copyArrayStorage):
(JSC::JSObject::putOwnDataPropertyBatching):
* Source/JavaScriptCore/runtime/JSObject.h:
(JSC::isEmptyJSFinalObject):
* Source/JavaScriptCore/runtime/JSObjectInlines.h:
(JSC::JSObject::fastForEachPropertyWithSideEffectFreeFunctor):
* Source/JavaScriptCore/runtime/ObjectConstructor.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):
* Source/JavaScriptCore/runtime/ObjectConstructorInlines.h:
(JSC::objectAssignFast):
* Source/JavaScriptCore/runtime/Structure.h:
* Source/JavaScriptCore/runtime/StructureInlines.h:
(JSC::Structure::canPerformFastPropertyEnumeration const):

Canonical link: https://commits.webkit.org/267797@main
  • Loading branch information
hyjorc1 committed Sep 8, 2023
1 parent 093d44d commit 3e8cc77
Show file tree
Hide file tree
Showing 18 changed files with 614 additions and 29 deletions.
5 changes: 5 additions & 0 deletions JSTests/microbenchmarks/object-assign-array-storage-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let o = { "0": "a", 1: "b", p1: 1, p2: 2, p3: 1, p4: 2, p5: 1, p6: 2, p7: 1, p8: 2, "1000": 1000 };
for (let i = 0; i < 1e4; i++) {
Object.assign({ "-3": -1 }, o);
Object.assign({}, o);
}
5 changes: 5 additions & 0 deletions JSTests/microbenchmarks/object-assign-contiguous-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let o = { "0": "a", "1": "a", "2": "a", "3": "a", "4": "a", "5": "a", "6": "a", "7": "a", "8": "a", };
for (let i = 0; i < 1e4; i++) {
Object.assign({}, o);
Object.assign({ "0": -1 }, o);
}
5 changes: 5 additions & 0 deletions JSTests/microbenchmarks/object-assign-double-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let o = { "0": 10.12, "1": 20.12, "2": 30.12, "3": 40.12, "4": 50.12, "5": 60.12, "6": 70.12, "7": 80.12, "8": 90.12, };
for (let i = 0; i < 1e4; i++) {
Object.assign({}, o);
Object.assign({ "3": -1 }, o);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let o = { "$shared": false, "borderWidth": 1, "hitRadius": 1, "hoverBorderWidth": 1, "hoverRadius": 4, "pointStyle": "circle", "radius": 3, "rotation": 0, "backgroundColor": "rgba(0, 125, 255, .20)", "borderColor": "rgba(0,0,0,0.1)", "1000": 1000 };
delete o["1000"];

const cloneIfNotShared = (cached, shared) => {
shared ? cached : Object.assign({}, cached)
};

for (let i = 0; i < 1e4; i++)
cloneIfNotShared(o, 0);
5 changes: 5 additions & 0 deletions JSTests/microbenchmarks/object-assign-int32-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let o = { "0": 1, "1": 2, "2": 3, "3": 4, "4": 5, "5": 6, "6": 7, "7": 8, "8": 9, };
for (let i = 0; i < 1e4; i++) {
Object.assign({}, o);
Object.assign({ "3": -1 }, o);
}
54 changes: 54 additions & 0 deletions JSTests/microbenchmarks/object-assign-multiple-sources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
function test(data) {
Object.assign(data.target, data.source1, data.source2);
}

let o1 = { "0": "a", 1: "b", p1: 1, p2: 2, p3: 1, p4: 2, p5: 1, p6: 2, p7: 1, p8: 2, "1000": 1000 };
let o2 = { "4": 2, p10: 1, "2000": 2000 };
let o = { a: 10 };
Object.defineProperties(o, {
"0": {
get: function () { return this.a; },
set: function (x) { this.a = x; },
},
});
o1.__proto__ = o;
o2.__proto__ = o;

let testData = [
// NonArrayWithInt32
{
target: { "3": -1 },
source1: { "0": 1, "1": 2, "2": 3, "3": 4, "4": 5, "5": 6, "6": 7, "7": 8, "8": 9, },
source2: { "9": 2, "10": 3, "11": 4, },
},
// NonArrayWithContiguous
{
target: { "0": -1 },
source1: { "0": "a", "1": "a", "2": "a", "3": "a", "4": "a", "5": "a", "6": "a", "7": "a", "8": "a", },
source2: { "9": "a", "10": "a", "11": "a", },
},
// NonArrayWithDouble
{
target: { "3": -1 },
source1: { "0": 10.12, "1": 20.12, "2": 30.12, "3": 40.12, "4": 50.12, "5": 60.12, "6": 70.12, "7": 80.12, "8": 90.12, },
source2: { "9": 10.12, "10": 20.12, "11": 30.12, },
},

// NonArrayWithArrayStorage
{
target: { "3": -1 },
source1: { "0": "a", 1: "b", p1: 1, p2: 2, p3: 1, p4: 2, p5: 1, p6: 2, p7: 1, p8: 2, "1000": 1000 },
source2: { "3": "a", 4: "b", p9: 1, p10: 2, "2000": 2000 },
},
// NonArrayWithSlowPutArrayStorage
{
target: { "3": -1 },
source1: o1,
source2: o2,
},
];

for (let i = 0; i < 1e4; i++) {
for (let data of testData)
test(data);
}
85 changes: 85 additions & 0 deletions JSTests/stress/object-assign-multiple-non-array-sources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//@ $skipModes << :lockdown
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error(`bad value: ${String(actual)}, expected ${String(expected)}`);
}

function test(data) {
shouldBe($vm.indexingMode(data.source1), data.sourceIndexingType);
shouldBe($vm.indexingMode(data.source2), data.sourceIndexingType);
let result = Object.assign(data.target, data.source1, data.source2);
if (data.verifyWithTarget)
for (const [key, value] of Object.entries(data.target))
shouldBe(result[key], value);
if (data.verifyWithSource) {
for (const [key, value] of Object.entries(data.source1))
shouldBe(result[key], value);
for (const [key, value] of Object.entries(data.source2))
shouldBe(result[key], value);
}
}

let o1 = { "0": "a", 1: "b", p1: 1, p2: 2, p3: 1, p4: 2, p5: 1, p6: 2, p7: 1, p8: 2, "1000": 1000 };
let o2 = { "4": 2, p10: 1, "2000": 2000 };
let o = { a: 10 };
Object.defineProperties(o, {
"0": {
get: function () { return this.a; },
set: function (x) { this.a = x; },
},
});
o1.__proto__ = o;
o2.__proto__ = o;

let testData = [
// NonArrayWithInt32
{
target: { "3": -1 },
source1: { "0": 1, "1": 2, "2": 3, "3": 4, "4": 5, "5": 6, "6": 7, "7": 8, "8": 9, },
source2: { "9": 2, "10": 3, "11": 4, },
sourceIndexingType: "NonArrayWithInt32",
verifyWithTarget: false,
verifyWithSource: true,
},
// NonArrayWithContiguous
{
target: { "0": -1 },
source1: { "0": "a", "1": "a", "2": "a", "3": "a", "4": "a", "5": "a", "6": "a", "7": "a", "8": "a", },
source2: { "9": "a", "10": "a", "11": "a", },
sourceIndexingType: "NonArrayWithContiguous",
verifyWithTarget: false,
verifyWithSource: true,
},
// NonArrayWithDouble
{
target: { "3": -1 },
source1: { "0": 10.12, "1": 20.12, "2": 30.12, "3": 40.12, "4": 50.12, "5": 60.12, "6": 70.12, "7": 80.12, "8": 90.12, },
source2: { "9": 10.12, "10": 20.12, "11": 30.12, },
sourceIndexingType: "NonArrayWithDouble",
verifyWithTarget: false,
verifyWithSource: true,
},
// NonArrayWithArrayStorage
{
target: { "3": -1 },
source1: { "0": "a", 1: "b", p1: 1, p2: 2, p3: 1, p4: 2, p5: 1, p6: 2, p7: 1, p8: 2, "1000": 1000 },
source2: { "3": "a", 4: "b", p9: 1, p10: 2, "2000": 2000 },
sourceIndexingType: "NonArrayWithArrayStorage",
verifyWithTarget: false,
verifyWithSource: true,
},
// NonArrayWithSlowPutArrayStorage
{
target: { "3": -1 },
source1: o1,
source2: o2,
sourceIndexingType: "NonArrayWithSlowPutArrayStorage",
verifyWithTarget: false,
verifyWithSource: true,
},
];

for (let i = 0; i < 1e4; i++) {
for (let data of testData)
test(data);
}
122 changes: 122 additions & 0 deletions JSTests/stress/object-assign-non-array-with-array-storage-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//@ $skipModes << :lockdown
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error(`bad value: ${String(actual)}, expected ${String(expected)}`);
}

function test(data) {
shouldBe($vm.indexingMode(data.source), data.sourceIndexingType);
let result = Object.assign(data.target, data.source);
if (data.verifyWithTarget)
for (const [key, value] of Object.entries(data.target))
shouldBe(result[key], value);
if (data.verifyWithSource)
for (const [key, value] of Object.entries(data.source))
shouldBe(result[key], value);
}

let o1 = { "0": "a", 1: "b", p1: 1, p2: 2, p3: 1, p4: 2, p5: 1, p6: 2, p7: 1, p8: 2, "1000": 1000 };
let o2 = { "0": 2, p1: 1, "1000": 1000 };
let o = { a: 10 };
Object.defineProperties(o, {
"0": {
get: function () { return this.a; },
set: function (x) { this.a = x; },
},
});
o1.__proto__ = o;
o2.__proto__ = o;

let testData = [
// NonArrayWithArrayStorage
{
target: { "3": -1 },
source: { "0": "a", 1: "b", p1: 1, p2: 2, p3: 1, p4: 2, p5: 1, p6: 2, p7: 1, p8: 2, "1000": 1000 },
sourceIndexingType: "NonArrayWithArrayStorage",
verifyWithTarget: false,
verifyWithSource: true,
},
{
target: { "0": 1, },
source: { "0": 2, p1: 1, "1000": 1000 },
sourceIndexingType: "NonArrayWithArrayStorage",
verifyWithTarget: false,
verifyWithSource: true,
},
{
target: { "-3": -1 },
source: { "0": "a", 1: "b", p1: 1, p2: 2, p3: 1, p4: 2, p5: 1, p6: 2, p7: 1, p8: 2, "1000": 1000 },
sourceIndexingType: "NonArrayWithArrayStorage",
verifyWithTarget: true,
verifyWithSource: true,
},
{
target: { p: 1 },
source: { "0": 2, p1: 1, "1000": 1000 },
sourceIndexingType: "NonArrayWithArrayStorage",
verifyWithTarget: true,
verifyWithSource: true,
},
{
target: { "0": 2 },
source: {},
sourceIndexingType: "NonArray",
verifyWithTarget: true,
verifyWithSource: false,
},
{
target: {},
source: { "0": 2, p1: 1, "1000": 1000 },
sourceIndexingType: "NonArrayWithArrayStorage",
verifyWithTarget: true,
verifyWithSource: true,
},
// NonArrayWithSlowPutArrayStorage
{
target: { "3": -1 },
source: o1,
sourceIndexingType: "NonArrayWithSlowPutArrayStorage",
verifyWithTarget: false,
verifyWithSource: true,
},
{
target: { "0": 1, },
source: o2,
sourceIndexingType: "NonArrayWithSlowPutArrayStorage",
verifyWithTarget: false,
verifyWithSource: true,
},
{
target: { "-3": -1 },
source: o1,
sourceIndexingType: "NonArrayWithSlowPutArrayStorage",
verifyWithTarget: true,
verifyWithSource: true,
},
{
target: { p: 1 },
source: o2,
sourceIndexingType: "NonArrayWithSlowPutArrayStorage",
verifyWithTarget: true,
verifyWithSource: true,
},
{
target: { "0": 2 },
source: {},
sourceIndexingType: "NonArray",
verifyWithTarget: true,
verifyWithSource: false,
},
{
target: {},
source: o2,
sourceIndexingType: "NonArrayWithSlowPutArrayStorage",
verifyWithTarget: true,
verifyWithSource: true,
},
];

for (let i = 0; i < 1e4; i++) {
for (let data of testData)
test(data);
}
Loading

0 comments on commit 3e8cc77

Please sign in to comment.