Skip to content

Commit 811d74a

Browse files
committed
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).
1 parent 42d0e13 commit 811d74a

File tree

1 file changed

+138
-124
lines changed

1 file changed

+138
-124
lines changed

lib/internal/util/comparisons.js

Lines changed: 138 additions & 124 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]) {
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,124 +302,136 @@ 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)) {
303-
return false;
304-
}
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)) {
309-
return false;
310-
}
311-
} else if (isRegExp(val1)) {
312-
if (!isRegExp(val2) || !areSimilarRegExps(val1, val2)) {
313-
return false;
314-
}
315-
} else if (isArrayBufferView(val1)) {
316-
if (TypedArrayPrototypeGetSymbolToStringTag(val1) !==
317-
TypedArrayPrototypeGetSymbolToStringTag(val2)) {
318-
return false;
319-
}
320-
if (mode === kPartial && val1.byteLength !== val2.byteLength) {
321-
if (!isPartialArrayBufferView(val1, val2)) {
305+
} else {
306+
let val1Tag;
307+
if (val1[SymbolToStringTag] === undefined &&
308+
(val1Tag = ObjectPrototypeToString(val1)) === '[object Object]') {
309+
if (slowHasUnequalTag(val1Tag, val1, val2)) {
322310
return false;
323311
}
324-
} else if (mode === kLoose &&
325-
(isFloat32Array(val1) || isFloat64Array(val1) || isFloat16Array(val1))) {
326-
if (!areSimilarFloatArrays(val1, val2)) {
312+
return keyCheck(val1, val2, mode, memos, kNoIterator);
313+
} else if (isSet(val1)) {
314+
if (!isSet(val2) ||
315+
(val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) ||
316+
hasUnequalTag(val1, val2)) {
327317
return false;
328318
}
329-
} else if (!areSimilarTypedArrays(val1, val2)) {
330-
return false;
331-
}
332-
// Buffer.compare returns true, so val1.length === val2.length. If they both
333-
// only contain numeric keys, we don't need to exam further than checking
334-
// the symbols.
335-
const filter = mode !== kLoose ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS;
336-
const keys2 = getOwnNonIndexProperties(val2, filter);
337-
if (mode !== kPartial &&
338-
keys2.length !== getOwnNonIndexProperties(val1, filter).length) {
339-
return false;
340-
}
341-
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))) {
345-
return false;
346-
}
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))) {
351-
return false;
352-
}
353-
return keyCheck(val1, val2, mode, memos, kIsMap);
354-
} else if (isAnyArrayBuffer(val1)) {
355-
if (!isAnyArrayBuffer(val2)) {
356-
return false;
357-
}
358-
if (mode !== kPartial || val1.byteLength === val2.byteLength) {
359-
if (!areEqualArrayBuffers(val1, val2)) {
319+
return keyCheck(val1, val2, mode, memos, kIsSet);
320+
} else if (isMap(val1)) {
321+
if (!isMap(val2) ||
322+
(val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) ||
323+
hasUnequalTag(val1, val2)) {
360324
return false;
361325
}
362-
} else if (!isPartialUint8Array(new Uint8Array(val1), new Uint8Array(val2))) {
363-
return false;
364-
}
365-
} else if (isError(val1)) {
366-
// Do not compare the stack as it might differ even though the error itself
367-
// is otherwise identical.
368-
if (!isError(val2) ||
369-
!isEnumerableOrIdentical(val1, val2, 'message', mode, memos) ||
370-
!isEnumerableOrIdentical(val1, val2, 'name', mode, memos) ||
371-
!isEnumerableOrIdentical(val1, val2, 'cause', mode, memos) ||
372-
!isEnumerableOrIdentical(val1, val2, 'errors', mode, memos)) {
373-
return false;
374-
}
375-
const hasOwnVal2Cause = hasOwn(val2, 'cause');
376-
if ((hasOwnVal2Cause !== hasOwn(val1, 'cause') && (mode !== kPartial || hasOwnVal2Cause))) {
377-
return false;
378-
}
379-
} else if (isBoxedPrimitive(val1)) {
380-
if (!isEqualBoxedPrimitive(val1, val2)) {
381-
return false;
382-
}
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;
394-
} else if (isURL(val1)) {
395-
if (!isURL(val2) || val1.href !== val2.href) {
396-
return false;
397-
}
398-
} else if (isKeyObject(val1)) {
399-
if (!isKeyObject(val2) || !val1.equals(val2)) {
326+
return keyCheck(val1, val2, mode, memos, kIsMap);
327+
} else if (isArrayBufferView(val1)) {
328+
if (TypedArrayPrototypeGetSymbolToStringTag(val1) !==
329+
TypedArrayPrototypeGetSymbolToStringTag(val2)) {
330+
return false;
331+
}
332+
if (mode === kPartial && val1.byteLength !== val2.byteLength) {
333+
if (!isPartialArrayBufferView(val1, val2)) {
334+
return false;
335+
}
336+
} else if (mode === kLoose &&
337+
(isFloat32Array(val1) || isFloat64Array(val1) || isFloat16Array(val1))) {
338+
if (!areSimilarFloatArrays(val1, val2)) {
339+
return false;
340+
}
341+
} else if (!areSimilarTypedArrays(val1, val2)) {
342+
return false;
343+
}
344+
// Buffer.compare returns true, so val1.length === val2.length. If they both
345+
// only contain numeric keys, we don't need to exam further than checking
346+
// the symbols.
347+
const filter = mode !== kLoose ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS;
348+
const keys2 = getOwnNonIndexProperties(val2, filter);
349+
if (mode !== kPartial &&
350+
keys2.length !== getOwnNonIndexProperties(val1, filter).length) {
351+
return false;
352+
}
353+
return keyCheck(val1, val2, mode, memos, kNoIterator, keys2);
354+
} else if (isDate(val1)) {
355+
if (!isDate(val2)) {
356+
return false;
357+
}
358+
const time1 = DatePrototypeGetTime(val1);
359+
const time2 = DatePrototypeGetTime(val2);
360+
// eslint-disable-next-line no-self-compare
361+
if (time1 !== time2 && (time1 === time1 || time2 === time2)) {
362+
return false;
363+
}
364+
} else if (isRegExp(val1)) {
365+
if (!isRegExp(val2) || !areSimilarRegExps(val1, val2) || hasUnequalTag(val1, val2)) {
366+
return false;
367+
}
368+
} else if (isAnyArrayBuffer(val1)) {
369+
if (!isAnyArrayBuffer(val2) || hasUnequalTag(val1, val2)) {
370+
return false;
371+
}
372+
if (mode !== kPartial || val1.byteLength === val2.byteLength) {
373+
if (!areEqualArrayBuffers(val1, val2)) {
374+
return false;
375+
}
376+
} else if (!isPartialUint8Array(new Uint8Array(val1), new Uint8Array(val2))) {
377+
return false;
378+
}
379+
} else if (slowHasUnequalTag(val1Tag ?? ObjectPrototypeToString(val1), val1, val2) ||
380+
ArrayIsArray(val2) ||
381+
isArrayBufferView(val2) ||
382+
isSet(val2) ||
383+
isMap(val2) ||
384+
isDate(val2) ||
385+
isRegExp(val2) ||
386+
isAnyArrayBuffer(val2)) {
400387
return false;
401-
}
402-
} else if (isCryptoKey(val1)) {
403-
if (kKeyObject === undefined) {
404-
kKeyObject = require('internal/crypto/util').kKeyObject;
405-
({ kExtractable, kAlgorithm, kKeyUsages } = require('internal/crypto/keys'));
406-
}
407-
if (!isCryptoKey(val2) ||
408-
val1[kExtractable] !== val2[kExtractable] ||
409-
!innerDeepEqual(val1[kAlgorithm], val2[kAlgorithm], mode, memos) ||
410-
!innerDeepEqual(val1[kKeyUsages], val2[kKeyUsages], mode, memos) ||
411-
!innerDeepEqual(val1[kKeyObject], val2[kKeyObject], mode, memos)
412-
) {
388+
} else if (isError(val1)) {
389+
// Do not compare the stack as it might differ even though the error itself
390+
// is otherwise identical.
391+
if (!isError(val2) ||
392+
!isEnumerableOrIdentical(val1, val2, 'message', mode, memos) ||
393+
!isEnumerableOrIdentical(val1, val2, 'name', mode, memos) ||
394+
!isEnumerableOrIdentical(val1, val2, 'cause', mode, memos) ||
395+
!isEnumerableOrIdentical(val1, val2, 'errors', mode, memos)) {
396+
return false;
397+
}
398+
const hasOwnVal2Cause = hasOwn(val2, 'cause');
399+
if ((hasOwnVal2Cause !== hasOwn(val1, 'cause') && (mode !== kPartial || hasOwnVal2Cause))) {
400+
return false;
401+
}
402+
} else if (isBoxedPrimitive(val1)) {
403+
if (!isEqualBoxedPrimitive(val1, val2)) {
404+
return false;
405+
}
406+
} else if (isURL(val1)) {
407+
if (!isURL(val2) || val1.href !== val2.href) {
408+
return false;
409+
}
410+
} else if (isKeyObject(val1)) {
411+
if (!isKeyObject(val2) || !val1.equals(val2)) {
412+
return false;
413+
}
414+
} else if (isCryptoKey(val1)) {
415+
if (kKeyObject === undefined) {
416+
kKeyObject = require('internal/crypto/util').kKeyObject;
417+
({ kExtractable, kAlgorithm, kKeyUsages } = require('internal/crypto/keys'));
418+
}
419+
if (!isCryptoKey(val2) ||
420+
val1[kExtractable] !== val2[kExtractable] ||
421+
!innerDeepEqual(val1[kAlgorithm], val2[kAlgorithm], mode, memos) ||
422+
!innerDeepEqual(val1[kKeyUsages], val2[kKeyUsages], mode, memos) ||
423+
!innerDeepEqual(val1[kKeyObject], val2[kKeyObject], mode, memos)
424+
) {
425+
return false;
426+
}
427+
} else if (isBoxedPrimitive(val2) ||
428+
isNativeError(val2) ||
429+
val2 instanceof Error ||
430+
isWeakMap(val1) ||
431+
isWeakSet(val1) ||
432+
isPromise(val1)) {
413433
return false;
414434
}
415-
} else if (isWeakMap(val1) || isWeakSet(val1) || isPromise(val1)) {
416-
return false;
417435
}
418436

419437
return keyCheck(val1, val2, mode, memos, kNoIterator);
@@ -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)