Skip to content

Commit c42ee7f

Browse files
committed
assert: improve partialDeepStrictEqual performance
This implements fast paths for typed arrays, array buffers and sets and maps that contain only objects as keys.
1 parent ab9660b commit c42ee7f

File tree

3 files changed

+44
-23
lines changed

3 files changed

+44
-23
lines changed

benchmark/assert/deepequal-buffer.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const bench = common.createBenchmark(main, {
77
len: [1e2, 1e3],
88
strict: [0, 1],
99
arrayBuffer: [0, 1],
10-
method: ['deepEqual', 'notDeepEqual', 'unequal_length'],
10+
method: ['deepEqual', 'notDeepEqual', 'unequal_length', 'partial'],
1111
}, {
1212
combinationFilter: (p) => {
1313
return p.strict === 1 || p.method === 'deepEqual';
@@ -18,11 +18,16 @@ function main({ len, n, method, strict, arrayBuffer }) {
1818
let actual = Buffer.alloc(len);
1919
let expected = Buffer.alloc(len + Number(method === 'unequal_length'));
2020

21-
2221
if (method === 'unequal_length') {
2322
method = 'notDeepEqual';
2423
}
2524

25+
if (method === 'partial') {
26+
method = 'partialDeepStrictEqual';
27+
} else if (strict) {
28+
method = method.replace('eep', 'eepStrict');
29+
}
30+
2631
for (let i = 0; i < len; i++) {
2732
actual.writeInt8(i % 128, i);
2833
expected.writeInt8(i % 128, i);
@@ -33,10 +38,6 @@ function main({ len, n, method, strict, arrayBuffer }) {
3338
expected[position] = expected[position] + 1;
3439
}
3540

36-
if (strict) {
37-
method = method.replace('eep', 'eepStrict');
38-
}
39-
4041
const fn = assert[method];
4142

4243
if (arrayBuffer) {

benchmark/assert/partial-deep-equal.js

+27-7
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ const common = require('../common.js');
44
const assert = require('assert');
55

66
const bench = common.createBenchmark(main, {
7-
n: [25],
7+
n: [125],
88
size: [500],
9-
extraProps: [0],
9+
extraProps: [0, 1],
1010
datasetName: [
1111
'objects',
1212
'sets',
13+
'setsWithObjects',
1314
'maps',
1415
'circularRefs',
1516
'typedArrays',
@@ -31,17 +32,29 @@ function createObjects(length, extraProps, depth = 0) {
3132
foo: 'yarp',
3233
nope: {
3334
bar: '123',
34-
...extraProps ? { a: [1, 2, i] } : {},
35+
...(extraProps ? { a: [1, 2, i] } : {}),
3536
c: {},
3637
b: !depth ? createObjects(2, extraProps, depth + 1) : [],
3738
},
3839
}));
3940
}
4041

42+
function createSetsWithObjects(length, extraProps, depth = 0) {
43+
return Array.from({ length }, (_, i) => new Set([
44+
...(extraProps ? [{}] : []),
45+
{
46+
simple: 'object',
47+
number: i,
48+
},
49+
['array', 'with', 'values'],
50+
new Set([[], {}, { nested: i }]),
51+
]));
52+
}
53+
4154
function createSets(length, extraProps, depth = 0) {
4255
return Array.from({ length }, (_, i) => new Set([
4356
'yarp',
44-
...extraProps ? ['123', 1, 2] : [],
57+
...(extraProps ? ['123', 1, 2] : []),
4558
i + 3,
4659
null,
4760
{
@@ -56,7 +69,7 @@ function createSets(length, extraProps, depth = 0) {
5669

5770
function createMaps(length, extraProps, depth = 0) {
5871
return Array.from({ length }, (_, i) => new Map([
59-
...extraProps ? [['primitiveKey', 'primitiveValue']] : [],
72+
...(extraProps ? [['primitiveKey', 'primitiveValue']] : []),
6073
[42, 'numberKey'],
6174
['objectValue', { a: 1, b: i }],
6275
['arrayValue', [1, 2, i]],
@@ -114,16 +127,23 @@ function createTypedArrays(length, extraParts) {
114127
}
115128

116129
function createArrayBuffers(length, extra) {
117-
return Array.from({ length }, (_, n) => new ArrayBuffer(n + extra ? 1 : 0));
130+
return Array.from({ length }, (_, n) => {
131+
const buffer = Buffer.alloc(n + (extra ? 1 : 0));
132+
for (let i = 0; i < n; i++) {
133+
buffer.writeInt8(i % 128, i);
134+
}
135+
return buffer.buffer;
136+
});
118137
}
119138

120139
function createDataViewArrayBuffers(length, extra) {
121-
return Array.from({ length }, (_, n) => new DataView(new ArrayBuffer(n + extra ? 1 : 0)));
140+
return createArrayBuffers(length, extra).map(buffer => new DataView(buffer));
122141
}
123142

124143
const datasetMappings = {
125144
objects: createObjects,
126145
sets: createSets,
146+
setsWithObjects: createSetsWithObjects,
127147
maps: createMaps,
128148
circularRefs: createCircularRefs,
129149
typedArrays: createTypedArrays,

lib/internal/util/comparisons.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ function innerDeepEqual(val1, val2, mode, memos) {
243243
TypedArrayPrototypeGetSymbolToStringTag(val2)) {
244244
return false;
245245
}
246-
if (mode === kPartial) {
246+
if (mode === kPartial && val1.byteLength !== val2.byteLength) {
247247
if (!isPartialArrayBufferView(val1, val2)) {
248248
return false;
249249
}
@@ -280,7 +280,7 @@ function innerDeepEqual(val1, val2, mode, memos) {
280280
if (!isAnyArrayBuffer(val2)) {
281281
return false;
282282
}
283-
if (mode !== kPartial) {
283+
if (mode !== kPartial || val1.byteLength === val2.byteLength) {
284284
if (!areEqualArrayBuffers(val1, val2)) {
285285
return false;
286286
}
@@ -546,18 +546,18 @@ function partialObjectSetEquiv(a, b, mode, set, memo) {
546546
}
547547

548548
function setObjectEquiv(a, b, mode, set, memo) {
549-
if (mode === kPartial) {
550-
return partialObjectSetEquiv(a, b, mode, set, memo);
551-
}
552549
// Fast path for objects only
553-
if (mode === kStrict && set.size === a.size) {
550+
if (mode !== kLoose && set.size === a.size) {
554551
for (const val of a) {
555552
if (!setHasEqualElement(set, val, mode, memo)) {
556553
return false;
557554
}
558555
}
559556
return true;
560557
}
558+
if (mode === kPartial) {
559+
return partialObjectSetEquiv(a, b, mode, set, memo);
560+
}
561561

562562
for (const val of a) {
563563
// Primitive values have already been handled above.
@@ -639,18 +639,18 @@ function partialObjectMapEquiv(a, b, mode, set, memo) {
639639
}
640640

641641
function mapObjectEquivalence(a, b, mode, set, memo) {
642-
if (mode === kPartial) {
643-
return partialObjectMapEquiv(a, b, mode, set, memo);
644-
}
645642
// Fast path for objects only
646-
if (mode === kStrict && set.size === a.size) {
643+
if (mode !== kLoose && set.size === a.size) {
647644
for (const { 0: key1, 1: item1 } of a) {
648645
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo)) {
649646
return false;
650647
}
651648
}
652649
return true;
653650
}
651+
if (mode === kPartial) {
652+
return partialObjectMapEquiv(a, b, mode, set, memo);
653+
}
654654
for (const { 0: key1, 1: item1 } of a) {
655655
if (typeof key1 === 'object' && key1 !== null) {
656656
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo))

0 commit comments

Comments
 (0)