Skip to content

Commit 5c8ce91

Browse files
authored
assert,util: improve comparison performance
This makes sure that the toStringTag symbol is used, if available instead of calculating the toString() value each time, if not needed (the type checks make a brand check, so there is no need to check the toStringTag, if non is defined on the object). PR-URL: #61176 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Marco Ippolito <[email protected]>
1 parent 879b95e commit 5c8ce91

File tree

1 file changed

+60
-46
lines changed

1 file changed

+60
-46
lines changed

lib/internal/util/comparisons.js

Lines changed: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const {
4141
StringPrototypeValueOf,
4242
Symbol,
4343
SymbolPrototypeValueOf,
44+
SymbolToStringTag,
4445
TypedArrayPrototypeGetByteLength: getByteLength,
4546
TypedArrayPrototypeGetSymbolToStringTag,
4647
Uint16Array,
@@ -264,6 +265,17 @@ function innerDeepEqual(val1, val2, mode, memos) {
264265
return objectComparisonStart(val1, val2, mode, memos);
265266
}
266267

268+
function hasUnequalTag(val1, val2) {
269+
return val1[SymbolToStringTag] !== val2[SymbolToStringTag];
270+
}
271+
272+
function slowHasUnequalTag(val1Tag, val1, val2) {
273+
if (val1[SymbolToStringTag] !== undefined && val2[SymbolToStringTag] !== undefined) {
274+
return val1[SymbolToStringTag] !== val2[SymbolToStringTag];
275+
}
276+
return val1Tag !== ObjectPrototypeToString(val2);
277+
}
278+
267279
function objectComparisonStart(val1, val2, mode, memos) {
268280
if (mode === kStrict) {
269281
if (wellKnownConstructors.has(val1.constructor) ||
@@ -276,16 +288,10 @@ function objectComparisonStart(val1, val2, mode, memos) {
276288
}
277289
}
278290

279-
const val1Tag = ObjectPrototypeToString(val1);
280-
const val2Tag = ObjectPrototypeToString(val2);
281-
282-
if (val1Tag !== val2Tag) {
283-
return false;
284-
}
285-
286291
if (ArrayIsArray(val1)) {
287292
if (!ArrayIsArray(val2) ||
288-
(val1.length !== val2.length && (mode !== kPartial || val1.length < val2.length))) {
293+
(val1.length !== val2.length && (mode !== kPartial || val1.length < val2.length)) ||
294+
hasUnequalTag(val1, val2)) {
289295
return false;
290296
}
291297

@@ -296,22 +302,29 @@ function objectComparisonStart(val1, val2, mode, memos) {
296302
return false;
297303
}
298304
return keyCheck(val1, val2, mode, memos, kIsArray, keys2);
299-
} else if (val1Tag === '[object Object]') {
300-
return keyCheck(val1, val2, mode, memos, kNoIterator);
301-
} else if (isDate(val1)) {
302-
if (!isDate(val2)) {
305+
}
306+
307+
let val1Tag;
308+
if (val1[SymbolToStringTag] === undefined &&
309+
(val1Tag = ObjectPrototypeToString(val1)) === '[object Object]') {
310+
if (slowHasUnequalTag(val1Tag, val1, val2)) {
303311
return false;
304312
}
305-
const time1 = DatePrototypeGetTime(val1);
306-
const time2 = DatePrototypeGetTime(val2);
307-
// eslint-disable-next-line no-self-compare
308-
if (time1 !== time2 && (time1 === time1 || time2 === time2)) {
313+
return keyCheck(val1, val2, mode, memos, kNoIterator);
314+
} else if (isSet(val1)) {
315+
if (!isSet(val2) ||
316+
(val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) ||
317+
hasUnequalTag(val1, val2)) {
309318
return false;
310319
}
311-
} else if (isRegExp(val1)) {
312-
if (!isRegExp(val2) || !areSimilarRegExps(val1, val2)) {
320+
return keyCheck(val1, val2, mode, memos, kIsSet);
321+
} else if (isMap(val1)) {
322+
if (!isMap(val2) ||
323+
(val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) ||
324+
hasUnequalTag(val1, val2)) {
313325
return false;
314326
}
327+
return keyCheck(val1, val2, mode, memos, kIsMap);
315328
} else if (isArrayBufferView(val1)) {
316329
if (TypedArrayPrototypeGetSymbolToStringTag(val1) !==
317330
TypedArrayPrototypeGetSymbolToStringTag(val2)) {
@@ -339,20 +352,22 @@ function objectComparisonStart(val1, val2, mode, memos) {
339352
return false;
340353
}
341354
return keyCheck(val1, val2, mode, memos, kNoIterator, keys2);
342-
} else if (isSet(val1)) {
343-
if (!isSet(val2) ||
344-
(val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size))) {
355+
} else if (isDate(val1)) {
356+
if (!isDate(val2) || hasUnequalTag(val1, val2)) {
345357
return false;
346358
}
347-
return keyCheck(val1, val2, mode, memos, kIsSet);
348-
} else if (isMap(val1)) {
349-
if (!isMap(val2) ||
350-
(val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size))) {
359+
const time1 = DatePrototypeGetTime(val1);
360+
const time2 = DatePrototypeGetTime(val2);
361+
// eslint-disable-next-line no-self-compare
362+
if (time1 !== time2 && (time1 === time1 || time2 === time2)) {
363+
return false;
364+
}
365+
} else if (isRegExp(val1)) {
366+
if (!isRegExp(val2) || !areSimilarRegExps(val1, val2) || hasUnequalTag(val1, val2)) {
351367
return false;
352368
}
353-
return keyCheck(val1, val2, mode, memos, kIsMap);
354369
} else if (isAnyArrayBuffer(val1)) {
355-
if (!isAnyArrayBuffer(val2)) {
370+
if (!isAnyArrayBuffer(val2) || hasUnequalTag(val1, val2)) {
356371
return false;
357372
}
358373
if (mode !== kPartial || val1.byteLength === val2.byteLength) {
@@ -362,6 +377,15 @@ function objectComparisonStart(val1, val2, mode, memos) {
362377
} else if (!isPartialUint8Array(new Uint8Array(val1), new Uint8Array(val2))) {
363378
return false;
364379
}
380+
} else if (slowHasUnequalTag(val1Tag ?? ObjectPrototypeToString(val1), val1, val2) ||
381+
ArrayIsArray(val2) ||
382+
isArrayBufferView(val2) ||
383+
isSet(val2) ||
384+
isMap(val2) ||
385+
isDate(val2) ||
386+
isRegExp(val2) ||
387+
isAnyArrayBuffer(val2)) {
388+
return false;
365389
} else if (isError(val1)) {
366390
// Do not compare the stack as it might differ even though the error itself
367391
// is otherwise identical.
@@ -380,17 +404,6 @@ function objectComparisonStart(val1, val2, mode, memos) {
380404
if (!isEqualBoxedPrimitive(val1, val2)) {
381405
return false;
382406
}
383-
} else if (ArrayIsArray(val2) ||
384-
isArrayBufferView(val2) ||
385-
isSet(val2) ||
386-
isMap(val2) ||
387-
isDate(val2) ||
388-
isRegExp(val2) ||
389-
isAnyArrayBuffer(val2) ||
390-
isBoxedPrimitive(val2) ||
391-
isNativeError(val2) ||
392-
val2 instanceof Error) {
393-
return false;
394407
} else if (isURL(val1)) {
395408
if (!isURL(val2) || val1.href !== val2.href) {
396409
return false;
@@ -412,7 +425,12 @@ function objectComparisonStart(val1, val2, mode, memos) {
412425
) {
413426
return false;
414427
}
415-
} else if (isWeakMap(val1) || isWeakSet(val1) || isPromise(val1)) {
428+
} else if (isBoxedPrimitive(val2) ||
429+
isNativeError(val2) ||
430+
val2 instanceof Error ||
431+
isWeakMap(val1) ||
432+
isWeakSet(val1) ||
433+
isPromise(val1)) {
416434
return false;
417435
}
418436

@@ -879,18 +897,16 @@ function partialSparseArrayEquiv(a, b, mode, memos, startA, startB) {
879897
let aPos = startA;
880898
const keysA = ObjectKeys(a);
881899
const keysB = ObjectKeys(b);
882-
const keysBLength = keysB.length;
883-
const keysALength = keysA.length;
884-
const lenA = keysALength - startA;
885-
const lenB = keysBLength - startB;
900+
const lenA = keysA.length - startA;
901+
const lenB = keysB.length - startB;
886902
if (lenA < lenB) {
887903
return false;
888904
}
889905
for (let i = 0; i < lenB; i++) {
890906
const keyB = keysB[startB + i];
891907
while (!innerDeepEqual(a[keysA[aPos]], b[keyB], mode, memos)) {
892908
aPos++;
893-
if (aPos > keysALength - lenB + i) {
909+
if (aPos > keysA.length - lenB + i) {
894910
return false;
895911
}
896912
}
@@ -922,8 +938,6 @@ function partialArrayEquiv(a, b, mode, memos) {
922938
}
923939

924940
function sparseArrayEquiv(a, b, mode, memos, i) {
925-
// TODO(BridgeAR): Use internal method to only get index properties. The
926-
// same applies to the partial implementation.
927941
const keysA = ObjectKeys(a);
928942
const keysB = ObjectKeys(b);
929943
if (keysA.length !== keysB.length) {

0 commit comments

Comments
 (0)