mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00
assert: improve partialDeepStrictEqual performance
This implements fast paths for typed arrays, array buffers and sets and maps that contain only objects as keys. PR-URL: https://github.com/nodejs/node/pull/57509 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
parent
ea9be17872
commit
1fbe3351ba
4 changed files with 55 additions and 24 deletions
|
@ -7,7 +7,7 @@ const bench = common.createBenchmark(main, {
|
|||
len: [1e2, 1e3],
|
||||
strict: [0, 1],
|
||||
arrayBuffer: [0, 1],
|
||||
method: ['deepEqual', 'notDeepEqual', 'unequal_length'],
|
||||
method: ['deepEqual', 'notDeepEqual', 'unequal_length', 'partial'],
|
||||
}, {
|
||||
combinationFilter: (p) => {
|
||||
return p.strict === 1 || p.method === 'deepEqual';
|
||||
|
@ -18,11 +18,16 @@ function main({ len, n, method, strict, arrayBuffer }) {
|
|||
let actual = Buffer.alloc(len);
|
||||
let expected = Buffer.alloc(len + Number(method === 'unequal_length'));
|
||||
|
||||
|
||||
if (method === 'unequal_length') {
|
||||
method = 'notDeepEqual';
|
||||
}
|
||||
|
||||
if (method === 'partial') {
|
||||
method = 'partialDeepStrictEqual';
|
||||
} else if (strict) {
|
||||
method = method.replace('eep', 'eepStrict');
|
||||
}
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
actual.writeInt8(i % 128, i);
|
||||
expected.writeInt8(i % 128, i);
|
||||
|
@ -33,10 +38,6 @@ function main({ len, n, method, strict, arrayBuffer }) {
|
|||
expected[position] = expected[position] + 1;
|
||||
}
|
||||
|
||||
if (strict) {
|
||||
method = method.replace('eep', 'eepStrict');
|
||||
}
|
||||
|
||||
const fn = assert[method];
|
||||
|
||||
if (arrayBuffer) {
|
||||
|
|
|
@ -4,12 +4,13 @@ const common = require('../common.js');
|
|||
const assert = require('assert');
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
n: [25],
|
||||
n: [125],
|
||||
size: [500],
|
||||
extraProps: [0],
|
||||
extraProps: [0, 1],
|
||||
datasetName: [
|
||||
'objects',
|
||||
'sets',
|
||||
'setsWithObjects',
|
||||
'maps',
|
||||
'circularRefs',
|
||||
'typedArrays',
|
||||
|
@ -31,17 +32,29 @@ function createObjects(length, extraProps, depth = 0) {
|
|||
foo: 'yarp',
|
||||
nope: {
|
||||
bar: '123',
|
||||
...extraProps ? { a: [1, 2, i] } : {},
|
||||
...(extraProps ? { a: [1, 2, i] } : {}),
|
||||
c: {},
|
||||
b: !depth ? createObjects(2, extraProps, depth + 1) : [],
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
function createSetsWithObjects(length, extraProps, depth = 0) {
|
||||
return Array.from({ length }, (_, i) => new Set([
|
||||
...(extraProps ? [{}] : []),
|
||||
{
|
||||
simple: 'object',
|
||||
number: i,
|
||||
},
|
||||
['array', 'with', 'values'],
|
||||
new Set([[], {}, { nested: i }]),
|
||||
]));
|
||||
}
|
||||
|
||||
function createSets(length, extraProps, depth = 0) {
|
||||
return Array.from({ length }, (_, i) => new Set([
|
||||
'yarp',
|
||||
...extraProps ? ['123', 1, 2] : [],
|
||||
...(extraProps ? ['123', 1, 2] : []),
|
||||
i + 3,
|
||||
null,
|
||||
{
|
||||
|
@ -56,7 +69,7 @@ function createSets(length, extraProps, depth = 0) {
|
|||
|
||||
function createMaps(length, extraProps, depth = 0) {
|
||||
return Array.from({ length }, (_, i) => new Map([
|
||||
...extraProps ? [['primitiveKey', 'primitiveValue']] : [],
|
||||
...(extraProps ? [['primitiveKey', 'primitiveValue']] : []),
|
||||
[42, 'numberKey'],
|
||||
['objectValue', { a: 1, b: i }],
|
||||
['arrayValue', [1, 2, i]],
|
||||
|
@ -114,16 +127,23 @@ function createTypedArrays(length, extraParts) {
|
|||
}
|
||||
|
||||
function createArrayBuffers(length, extra) {
|
||||
return Array.from({ length }, (_, n) => new ArrayBuffer(n + extra ? 1 : 0));
|
||||
return Array.from({ length }, (_, n) => {
|
||||
const buffer = Buffer.alloc(n + (extra ? 1 : 0));
|
||||
for (let i = 0; i < n; i++) {
|
||||
buffer.writeInt8(i % 128, i);
|
||||
}
|
||||
return buffer.buffer;
|
||||
});
|
||||
}
|
||||
|
||||
function createDataViewArrayBuffers(length, extra) {
|
||||
return Array.from({ length }, (_, n) => new DataView(new ArrayBuffer(n + extra ? 1 : 0)));
|
||||
return createArrayBuffers(length, extra).map((buffer) => new DataView(buffer));
|
||||
}
|
||||
|
||||
const datasetMappings = {
|
||||
objects: createObjects,
|
||||
sets: createSets,
|
||||
setsWithObjects: createSetsWithObjects,
|
||||
maps: createMaps,
|
||||
circularRefs: createCircularRefs,
|
||||
typedArrays: createTypedArrays,
|
||||
|
|
|
@ -243,7 +243,7 @@ function innerDeepEqual(val1, val2, mode, memos) {
|
|||
TypedArrayPrototypeGetSymbolToStringTag(val2)) {
|
||||
return false;
|
||||
}
|
||||
if (mode === kPartial) {
|
||||
if (mode === kPartial && val1.byteLength !== val2.byteLength) {
|
||||
if (!isPartialArrayBufferView(val1, val2)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ function innerDeepEqual(val1, val2, mode, memos) {
|
|||
if (!isAnyArrayBuffer(val2)) {
|
||||
return false;
|
||||
}
|
||||
if (mode !== kPartial) {
|
||||
if (mode !== kPartial || val1.byteLength === val2.byteLength) {
|
||||
if (!areEqualArrayBuffers(val1, val2)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -546,11 +546,8 @@ function partialObjectSetEquiv(a, b, mode, set, memo) {
|
|||
}
|
||||
|
||||
function setObjectEquiv(a, b, mode, set, memo) {
|
||||
if (mode === kPartial) {
|
||||
return partialObjectSetEquiv(a, b, mode, set, memo);
|
||||
}
|
||||
// Fast path for objects only
|
||||
if (mode === kStrict && set.size === a.size) {
|
||||
if (mode !== kLoose && set.size === a.size) {
|
||||
for (const val of a) {
|
||||
if (!setHasEqualElement(set, val, mode, memo)) {
|
||||
return false;
|
||||
|
@ -558,6 +555,9 @@ function setObjectEquiv(a, b, mode, set, memo) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
if (mode === kPartial) {
|
||||
return partialObjectSetEquiv(a, b, mode, set, memo);
|
||||
}
|
||||
|
||||
for (const val of a) {
|
||||
// Primitive values have already been handled above.
|
||||
|
@ -639,11 +639,8 @@ function partialObjectMapEquiv(a, b, mode, set, memo) {
|
|||
}
|
||||
|
||||
function mapObjectEquivalence(a, b, mode, set, memo) {
|
||||
if (mode === kPartial) {
|
||||
return partialObjectMapEquiv(a, b, mode, set, memo);
|
||||
}
|
||||
// Fast path for objects only
|
||||
if (mode === kStrict && set.size === a.size) {
|
||||
if (mode !== kLoose && set.size === a.size) {
|
||||
for (const { 0: key1, 1: item1 } of a) {
|
||||
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo)) {
|
||||
return false;
|
||||
|
@ -651,6 +648,9 @@ function mapObjectEquivalence(a, b, mode, set, memo) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
if (mode === kPartial) {
|
||||
return partialObjectMapEquiv(a, b, mode, set, memo);
|
||||
}
|
||||
for (const { 0: key1, 1: item1 } of a) {
|
||||
if (typeof key1 === 'object' && key1 !== null) {
|
||||
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo))
|
||||
|
|
|
@ -280,6 +280,11 @@ describe('Object Comparison Tests', () => {
|
|||
[{ a: 1 }, 'value1'],
|
||||
]),
|
||||
},
|
||||
{
|
||||
description: 'throws for Maps with mixed unequal entries',
|
||||
actual: new Map([[{ a: 2 }, 1], [1, 1], [{ b: 1 }, 1], [[], 1], [2, 1], [{ a: 1 }, 1]]),
|
||||
expected: new Map([[{ a: 1 }, 1], [[], 1], [2, 1], [{ a: 1 }, 1]]),
|
||||
},
|
||||
{
|
||||
description: 'throws for sets with different object values',
|
||||
actual: new Set([
|
||||
|
@ -494,6 +499,11 @@ describe('Object Comparison Tests', () => {
|
|||
actual: new Float32Array([+0.0]),
|
||||
expected: new Float32Array([-0.0]),
|
||||
},
|
||||
{
|
||||
description: 'throws when comparing two Uint8Array objects with non-matching entries',
|
||||
actual: { typedArray: new Uint8Array([1, 2, 3, 4, 5]) },
|
||||
expected: { typedArray: new Uint8Array([1, 333, 2, 4]) },
|
||||
},
|
||||
{
|
||||
description: 'throws when comparing two different urls',
|
||||
actual: new URL('http://foo'),
|
||||
|
@ -713,7 +723,7 @@ describe('Object Comparison Tests', () => {
|
|||
{
|
||||
description: 'compares two Uint8Array objects',
|
||||
actual: { typedArray: new Uint8Array([1, 2, 3, 4, 5]) },
|
||||
expected: { typedArray: new Uint8Array([1, 2, 3]) },
|
||||
expected: { typedArray: new Uint8Array([1, 2, 3, 5]) },
|
||||
},
|
||||
{
|
||||
description: 'compares two Int16Array objects',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue