Skip to content

Commit e102f40

Browse files
committed
assert,util: improve deep equal comparison performance
This allows the compiler to inline parts of the code in a way that especially primitives in arrays can be compared faster.
1 parent 2b52ea7 commit e102f40

File tree

1 file changed

+26
-30
lines changed

1 file changed

+26
-30
lines changed

lib/internal/util/comparisons.js

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -164,18 +164,6 @@ function isEnumerableOrIdentical(val1, val2, prop, mode, memos, method) {
164164
innerDeepEqual(val1[prop], val2[prop], mode, memos);
165165
}
166166

167-
// Notes: Type tags are historical [[Class]] properties that can be set by
168-
// FunctionTemplate::SetClassName() in C++ or Symbol.toStringTag in JS
169-
// and retrieved using Object.prototype.toString.call(obj) in JS
170-
// See https://tc39.github.io/ecma262/#sec-object.prototype.tostring
171-
// for a list of tags pre-defined in the spec.
172-
// There are some unspecified tags in the wild too (e.g. typed array tags).
173-
// Since tags can be altered, they only serve fast failures
174-
//
175-
// For strict comparison, objects should have
176-
// a) The same built-in type tag.
177-
// b) The same prototypes.
178-
179167
function innerDeepEqual(val1, val2, mode, memos) {
180168
// All identical values are equivalent, as determined by ===.
181169
if (val1 === val2) {
@@ -192,11 +180,7 @@ function innerDeepEqual(val1, val2, mode, memos) {
192180
if (typeof val2 !== 'object' ||
193181
typeof val1 !== 'object' ||
194182
val1 === null ||
195-
val2 === null ||
196-
(mode === kStrict &&
197-
(val1.constructor !== val2.constructor ||
198-
(val1.constructor === undefined &&
199-
ObjectGetPrototypeOf(val1) !== ObjectGetPrototypeOf(val2))))) {
183+
val2 === null) {
200184
return false;
201185
}
202186
} else {
@@ -210,6 +194,17 @@ function innerDeepEqual(val1, val2, mode, memos) {
210194
return false;
211195
}
212196
}
197+
return objectComparisonStart(val1, val2, mode, memos);
198+
}
199+
200+
function objectComparisonStart(val1, val2, mode, memos) {
201+
if (mode === kStrict &&
202+
(val1.constructor !== val2.constructor ||
203+
(val1.constructor === undefined &&
204+
ObjectGetPrototypeOf(val1) !== ObjectGetPrototypeOf(val2)))) {
205+
return false;
206+
}
207+
213208
const val1Tag = ObjectPrototypeToString(val1);
214209
const val2Tag = ObjectPrototypeToString(val2);
215210

@@ -218,7 +213,6 @@ function innerDeepEqual(val1, val2, mode, memos) {
218213
}
219214

220215
if (ArrayIsArray(val1)) {
221-
// Check for sparse arrays and general fast path
222216
if (!ArrayIsArray(val2) ||
223217
(val1.length !== val2.length && (mode !== kPartial || val1.length < val2.length))) {
224218
return false;
@@ -473,9 +467,9 @@ function keyCheck(val1, val2, mode, memos, iterationType, keys2) {
473467
return areEq;
474468
}
475469

476-
function setHasEqualElement(set, val1, mode, memo) {
470+
function setHasEqualElement(set, val1, mode, memo, fn) {
477471
for (const val2 of set) {
478-
if (innerDeepEqual(val1, val2, mode, memo)) {
472+
if (fn(val1, val2, mode, memo)) {
479473
// Remove the matching element to make sure we do not check that again.
480474
set.delete(val2);
481475
return true;
@@ -537,7 +531,7 @@ function partialObjectSetEquiv(a, b, mode, set, memo) {
537531
let aPos = 0;
538532
for (const val of a) {
539533
aPos++;
540-
if (!b.has(val) && setHasEqualElement(set, val, mode, memo) && set.size === 0) {
534+
if (!b.has(val) && setHasEqualElement(set, val, mode, memo, innerDeepEqual) && set.size === 0) {
541535
return true;
542536
}
543537
if (a.size - aPos < set.size) {
@@ -552,7 +546,7 @@ function setObjectEquiv(a, b, mode, set, memo) {
552546
// Fast path for objects only
553547
if (mode !== kLoose && set.size === a.size) {
554548
for (const val of a) {
555-
if (!setHasEqualElement(set, val, mode, memo)) {
549+
if (!setHasEqualElement(set, val, mode, memo, objectComparisonStart)) {
556550
return false;
557551
}
558552
}
@@ -562,15 +556,16 @@ function setObjectEquiv(a, b, mode, set, memo) {
562556
return partialObjectSetEquiv(a, b, mode, set, memo);
563557
}
564558

559+
const fn = mode === kStrict ? objectComparisonStart : innerDeepEqual;
565560
for (const val of a) {
566561
// Primitive values have already been handled above.
567562
if (typeof val === 'object') {
568-
if (!b.has(val) && !setHasEqualElement(set, val, mode, memo)) {
563+
if (!b.has(val) && !setHasEqualElement(set, val, mode, memo, fn)) {
569564
return false;
570565
}
571566
} else if (mode === kLoose &&
572567
!b.has(val) &&
573-
!setHasEqualElement(set, val, mode, memo)) {
568+
!setHasEqualElement(set, val, mode, memo, innerDeepEqual)) {
574569
return false;
575570
}
576571
}
@@ -626,12 +621,12 @@ function setEquiv(a, b, mode, memo) {
626621
return true;
627622
}
628623

629-
function mapHasEqualEntry(set, map, key1, item1, mode, memo) {
624+
function mapHasEqualEntry(set, map, key1, item1, mode, memo, fn) {
630625
// To be able to handle cases like:
631626
// Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
632627
// ... we need to consider *all* matching keys, not just the first we find.
633628
for (const key2 of set) {
634-
if (innerDeepEqual(key1, key2, mode, memo) &&
629+
if (fn(key1, key2, mode, memo) &&
635630
innerDeepEqual(item1, map.get(key2), mode, memo)) {
636631
set.delete(key2);
637632
return true;
@@ -647,7 +642,7 @@ function partialObjectMapEquiv(a, b, mode, set, memo) {
647642
aPos++;
648643
if (typeof key1 === 'object' &&
649644
key1 !== null &&
650-
mapHasEqualEntry(set, b, key1, item1, mode, memo) &&
645+
mapHasEqualEntry(set, b, key1, item1, mode, memo, objectComparisonStart) &&
651646
set.size === 0) {
652647
return true;
653648
}
@@ -663,7 +658,7 @@ function mapObjectEquivalence(a, b, mode, set, memo) {
663658
// Fast path for objects only
664659
if (mode !== kLoose && set.size === a.size) {
665660
for (const { 0: key1, 1: item1 } of a) {
666-
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo)) {
661+
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo, objectComparisonStart)) {
667662
return false;
668663
}
669664
}
@@ -672,16 +667,17 @@ function mapObjectEquivalence(a, b, mode, set, memo) {
672667
if (mode === kPartial) {
673668
return partialObjectMapEquiv(a, b, mode, set, memo);
674669
}
670+
const fn = mode === kStrict ? objectComparisonStart : innerDeepEqual;
675671
for (const { 0: key1, 1: item1 } of a) {
676672
if (typeof key1 === 'object' && key1 !== null) {
677-
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo))
673+
if (!mapHasEqualEntry(set, b, key1, item1, mode, memo, fn))
678674
return false;
679675
} else if (set.size === 0) {
680676
return true;
681677
} else if (mode === kLoose &&
682678
(!b.has(key1) ||
683679
!innerDeepEqual(item1, b.get(key1), mode, memo)) &&
684-
!mapHasEqualEntry(set, b, key1, item1, mode, memo)) {
680+
!mapHasEqualEntry(set, b, key1, item1, mode, memo, innerDeepEqual)) {
685681
return false;
686682
}
687683
}

0 commit comments

Comments
 (0)