Skip to content

Commit c9b2d34

Browse files
committed
assert,util: improve (partial) deep equal comparison performance
The circular check is now done lazily. That way only users that actually make use of such structures have to do the calculation overhead. It is an initial overhead the very first time it's run to detect the circular structure, while every following call uses the check for circular structures by default. This improves the performance for object comparison significantly. On top of that, this includes an optimised algorithm for sets and maps that contain objects as keys. The tracking is now done in an array and the array size is not changed when elements at the start or at the end of the array is detected. The order of the elements matter, so a reversed key order is now a magnitude faster. Insert sort comparison is also signficantly faster, random order is a tad faster.
1 parent 16aba24 commit c9b2d34

File tree

3 files changed

+256
-137
lines changed

3 files changed

+256
-137
lines changed

benchmark/assert/deepequal-map.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function benchmark(method, n, values, values2) {
3131
}
3232

3333
function main({ n, len, method, strict }) {
34-
const array = Array(len).fill(1);
34+
const array = Array.from({ length: len }, () => '');
3535

3636
switch (method) {
3737
case 'deepEqual_primitiveOnly': {

benchmark/assert/deepequal-set.js

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ const { deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual } =
66

77
const bench = common.createBenchmark(main, {
88
n: [1e3],
9-
len: [2, 5e2],
9+
len: [2, 1e2],
1010
strict: [0, 1],
11+
order: ['insert', 'random', 'reversed'],
1112
method: [
1213
'deepEqual_primitiveOnly',
1314
'deepEqual_objectOnly',
@@ -16,12 +17,30 @@ const bench = common.createBenchmark(main, {
1617
'notDeepEqual_objectOnly',
1718
'notDeepEqual_mixed',
1819
],
20+
}, {
21+
combinationFilter(p) {
22+
return p.order !== 'random' || p.strict === 1 && p.method !== 'notDeepEqual_objectOnly';
23+
},
1924
});
2025

21-
function benchmark(method, n, values, values2) {
26+
function shuffleArray(array) {
27+
for (let i = array.length - 1; i > 0; i--) {
28+
const j = Math.floor(Math.random() * (i + 1));
29+
const temp = array[i];
30+
array[i] = array[j];
31+
array[j] = temp;
32+
}
33+
}
34+
35+
function benchmark(method, n, values, values2, order) {
2236
const actual = new Set(values);
2337
// Prevent reference equal elements
24-
const deepCopy = JSON.parse(JSON.stringify(values2 ? values2 : values));
38+
let deepCopy = JSON.parse(JSON.stringify(values2));
39+
if (order === 'reversed') {
40+
deepCopy = deepCopy.reverse();
41+
} else if (order === 'random') {
42+
shuffleArray(deepCopy);
43+
}
2544
const expected = new Set(deepCopy);
2645
bench.start();
2746
for (let i = 0; i < n; ++i) {
@@ -30,39 +49,39 @@ function benchmark(method, n, values, values2) {
3049
bench.end(n);
3150
}
3251

33-
function main({ n, len, method, strict }) {
34-
const array = Array(len).fill(1);
52+
function main({ n, len, method, strict, order }) {
53+
const array = Array.from({ length: len }, () => '');
3554

3655
switch (method) {
3756
case 'deepEqual_primitiveOnly': {
3857
const values = array.map((_, i) => `str_${i}`);
39-
benchmark(strict ? deepStrictEqual : deepEqual, n, values);
58+
benchmark(strict ? deepStrictEqual : deepEqual, n, values, values, order);
4059
break;
4160
}
4261
case 'deepEqual_objectOnly': {
4362
const values = array.map((_, i) => [`str_${i}`, null]);
44-
benchmark(strict ? deepStrictEqual : deepEqual, n, values);
63+
benchmark(strict ? deepStrictEqual : deepEqual, n, values, values, order);
4564
break;
4665
}
4766
case 'deepEqual_mixed': {
4867
const values = array.map((_, i) => {
4968
return i % 2 ? [`str_${i}`, null] : `str_${i}`;
5069
});
51-
benchmark(strict ? deepStrictEqual : deepEqual, n, values);
70+
benchmark(strict ? deepStrictEqual : deepEqual, n, values, values, order);
5271
break;
5372
}
5473
case 'notDeepEqual_primitiveOnly': {
5574
const values = array.map((_, i) => `str_${i}`);
5675
const values2 = values.slice(0);
5776
values2[Math.floor(len / 2)] = 'w00t';
58-
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2);
77+
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2, order);
5978
break;
6079
}
6180
case 'notDeepEqual_objectOnly': {
6281
const values = array.map((_, i) => [`str_${i}`, null]);
6382
const values2 = values.slice(0);
6483
values2[Math.floor(len / 2)] = ['w00t'];
65-
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2);
84+
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2, order);
6685
break;
6786
}
6887
case 'notDeepEqual_mixed': {
@@ -71,7 +90,7 @@ function main({ n, len, method, strict }) {
7190
});
7291
const values2 = values.slice();
7392
values2[0] = 'w00t';
74-
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2);
93+
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2, order);
7594
break;
7695
}
7796
default:

0 commit comments

Comments
 (0)