Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
assert,util: improve performance
This improves the performance for array comparison by making the
sparse array detection simpler. On top of that it adds a fast path
for sets and maps that only contain objects as key.
  • Loading branch information
BridgeAR committed Mar 12, 2025
commit 5c40578930d478555d9bcd26667d2a2361183fdf
129 changes: 83 additions & 46 deletions lib/internal/util/comparisons.js
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,35 @@ 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) {
for (const val of a) {
if (!setHasEqualElement(set, val, mode, memo)) {
return false;
}
}
return true;
}

for (const val of a) {
// Primitive values have already been handled above.
if (typeof val === 'object') {
if (!b.has(val) && !setHasEqualElement(set, val, mode, memo)) {
return false;
}
} else if (mode === kLoose &&
!b.has(val) &&
!setHasEqualElement(set, val, mode, memo)) {
return false;
}
}
return set.size === 0;
}

function setEquiv(a, b, mode, memo) {
// This is a lazily initiated Set of entries which have to be compared
// pairwise.
Expand All @@ -584,22 +613,7 @@ function setEquiv(a, b, mode, memo) {
}

if (set !== null) {
if (mode === kPartial) {
return partialObjectSetEquiv(a, b, mode, set, memo);
}
for (const val of a) {
// Primitive values have already been handled above.
if (typeof val === 'object' && val !== null) {
if (!b.has(val) && !setHasEqualElement(set, val, mode, memo)) {
return false;
}
} else if (mode === kLoose &&
!b.has(val) &&
!setHasEqualElement(set, val, mode, memo)) {
return false;
}
}
return set.size === 0;
return setObjectEquiv(a, b, mode, set, memo);
}

return true;
Expand Down Expand Up @@ -640,6 +654,35 @@ 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) {
for (const { 0: key1, 1: item1 } of a) {
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo)) {
return false;
}
}
return true;
}
for (const { 0: key1, 1: item1 } of a) {
if (typeof key1 === 'object' && key1 !== null) {
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo))
return false;
} else if (set.size === 0) {
return true;
} else if (mode === kLoose &&
(!b.has(key1) ||
!innerDeepEqual(item1, b.get(key1), mode, memo)) &&
!mapHasEqualEntry(set, b, key1, item1, mode, memo)) {
return false;
}
}
return set.size === 0;
}

function mapEquiv(a, b, mode, memo) {
let set = null;

Expand Down Expand Up @@ -675,21 +718,7 @@ function mapEquiv(a, b, mode, memo) {
}

if (set !== null) {
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))
return false;
} else if (mode === kLoose &&
(!b.has(key1) ||
!innerDeepEqual(item1, b.get(key1), mode, memo)) &&
!mapHasEqualEntry(set, b, key1, item1, mode, memo)) {
return false;
}
}
return set.size === 0;
return mapObjectEquivalence(a, b, mode, set, memo);
}

return true;
Expand Down Expand Up @@ -737,6 +766,24 @@ function partialArrayEquiv(a, b, mode, memos) {
return true;
}

function sparseArrayEquiv(a, b, mode, memos, i) {
// TODO(BridgeAR): Use internal method to only get index properties. The
// same applies to the partial implementation.
const keysA = ObjectKeys(a);
const keysB = ObjectKeys(b);
if (keysA.length !== keysB.length) {
return false;
}
for (; i < keysA.length; i++) {
const key = keysA[i];
if (!ObjectPrototypeHasOwnProperty(b, key) ||
!innerDeepEqual(a[key], b[key], mode, memos)) {
return false;
}
}
return true;
}

function objEquiv(a, b, mode, keys2, memos, iterationType) {
// The pair must have equivalent values for every corresponding key.
if (keys2.length > 0) {
Expand All @@ -755,23 +802,13 @@ function objEquiv(a, b, mode, keys2, memos, iterationType) {
if (!innerDeepEqual(a[i], b[i], mode, memos)) {
return false;
}
const isOwnProperty = ObjectPrototypeHasOwnProperty(a, i);
if (isOwnProperty !== ObjectPrototypeHasOwnProperty(b, i)) {
const isSparseA = a[i] === undefined && !ObjectPrototypeHasOwnProperty(a, i);
const isSparseB = b[i] === undefined && !ObjectPrototypeHasOwnProperty(b, i);
if (isSparseA !== isSparseB) {
return false;
}
if (!isOwnProperty) {
// Array is sparse.
// TODO(BridgeAR): Use internal method to only get index properties. The
// same applies to the partial implementation.
const keysA = ObjectKeys(a);
for (; i < keysA.length; i++) {
const key = keysA[i];
if (!ObjectPrototypeHasOwnProperty(b, key) ||
!innerDeepEqual(a[key], b[key], mode, memos)) {
return false;
}
}
return keysA.length === ObjectKeys(b).length;
if (isSparseA) {
return sparseArrayEquiv(a, b, mode, memos, i);
}
}
} else if (iterationType === kIsSet) {
Expand Down