Skip to content

Commit 2b1cb3b

Browse files
BridgeARtargos
authored andcommitted
util,assert: improve performance
This significantly improves regular and typed array performance by not checking the indices keys anymore. This can be done with a V8 API that allows to only retrieve the non indices property keys. PR-URL: #22197 Reviewed-By: Ujjwal Sharma <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent c17e980 commit 2b1cb3b

File tree

3 files changed

+73
-44
lines changed

3 files changed

+73
-44
lines changed

lib/internal/util/comparisons.js

+37-44
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { compare } = process.binding('buffer');
44
const { isArrayBufferView } = require('internal/util/types');
55
const { internalBinding } = require('internal/bootstrap/loaders');
66
const { isDate, isMap, isRegExp, isSet } = internalBinding('types');
7+
const { getOwnNonIndexProperties } = process.binding('util');
78

89
const ReflectApply = Reflect.apply;
910

@@ -106,24 +107,11 @@ function strictDeepEqual(val1, val2, memos) {
106107
if (val1.length !== val2.length) {
107108
return false;
108109
}
109-
const keys = objectKeys(val1);
110-
if (keys.length !== objectKeys(val2).length) {
110+
const keys1 = getOwnNonIndexProperties(val1);
111+
if (keys1.length !== getOwnNonIndexProperties(val2).length) {
111112
return false;
112113
}
113-
// Fast path for non sparse arrays (no key comparison for indices
114-
// properties).
115-
// See https://tc39.github.io/ecma262/#sec-ordinaryownpropertykeys
116-
if (val1.length === keys.length) {
117-
if (keys.length === 0 || keys[val1.length - 1] === `${val1.length - 1}`) {
118-
return keyCheck(val1, val2, kStrict, memos, kIsArray, []);
119-
}
120-
} else if (keys.length > val1.length &&
121-
keys[val1.length - 1] === `${val1.length - 1}`) {
122-
const minimalKeys = keys.slice(val1.length);
123-
return keyCheck(val1, val2, kStrict, memos, kIsArray, minimalKeys);
124-
}
125-
// Only set this to kIsArray in case the array is not sparse!
126-
return keyCheck(val1, val2, kStrict, memos, kNoIterator, keys);
114+
return keyCheck(val1, val2, kStrict, memos, kIsArray, keys1);
127115
}
128116
if (val1Tag === '[object Object]') {
129117
return keyCheck(val1, val2, kStrict, memos, kNoIterator);
@@ -148,18 +136,14 @@ function strictDeepEqual(val1, val2, memos) {
148136
if (!areSimilarTypedArrays(val1, val2)) {
149137
return false;
150138
}
151-
// Buffer.compare returns true, so val1.length === val2.length
152-
// if they both only contain numeric keys, we don't need to exam further.
153-
const keys = objectKeys(val1);
154-
if (keys.length !== objectKeys(val2).length) {
139+
// Buffer.compare returns true, so val1.length === val2.length. If they both
140+
// only contain numeric keys, we don't need to exam further than checking
141+
// the symbols.
142+
const keys1 = getOwnNonIndexProperties(val1);
143+
if (keys1.length !== getOwnNonIndexProperties(val2).length) {
155144
return false;
156145
}
157-
if (keys.length === val1.length) {
158-
return keyCheck(val1, val2, kStrict, memos, kNoIterator, []);
159-
}
160-
// Only compare the special keys.
161-
const minimalKeys = keys.slice(val1.length);
162-
return keyCheck(val1, val2, kStrict, memos, kNoIterator, minimalKeys);
146+
return keyCheck(val1, val2, kStrict, memos, kNoIterator, keys1);
163147
} else if (isSet(val1)) {
164148
if (!isSet(val2) || val1.size !== val2.size) {
165149
return false;
@@ -173,21 +157,10 @@ function strictDeepEqual(val1, val2, memos) {
173157
// TODO: Make the valueOf checks safe.
174158
} else if (typeof val1.valueOf === 'function') {
175159
const val1Value = val1.valueOf();
176-
if (val1Value !== val1) {
177-
if (typeof val2.valueOf !== 'function') {
178-
return false;
179-
}
180-
if (!innerDeepEqual(val1Value, val2.valueOf(), true))
181-
return false;
182-
// Fast path for boxed primitive strings.
183-
if (typeof val1Value === 'string') {
184-
const keys = objectKeys(val1);
185-
if (keys.length !== objectKeys(val2).length) {
186-
return false;
187-
}
188-
const minimalKeys = keys.slice(val1.length);
189-
return keyCheck(val1, val2, kStrict, memos, kNoIterator, minimalKeys);
190-
}
160+
if (val1Value !== val1 &&
161+
(typeof val2.valueOf !== 'function' ||
162+
!innerDeepEqual(val1Value, val2.valueOf(), kStrict))) {
163+
return false;
191164
}
192165
}
193166
return keyCheck(val1, val2, kStrict, memos, kNoIterator);
@@ -277,7 +250,7 @@ function keyCheck(val1, val2, strict, memos, iterationType, aKeys) {
277250
}
278251
}
279252

280-
if (strict) {
253+
if (strict && arguments.length === 5) {
281254
const symbolKeysA = getOwnPropertySymbols(val1);
282255
if (symbolKeysA.length !== 0) {
283256
let count = 0;
@@ -310,7 +283,7 @@ function keyCheck(val1, val2, strict, memos, iterationType, aKeys) {
310283
if (aKeys.length === 0 &&
311284
(iterationType === kNoIterator ||
312285
iterationType === kIsArray && val1.length === 0 ||
313-
val1.size === 0)) {
286+
val1.size === 0)) {
314287
return true;
315288
}
316289

@@ -578,8 +551,28 @@ function objEquiv(a, b, strict, keys, memos, iterationType) {
578551
}
579552
} else if (iterationType === kIsArray) {
580553
for (; i < a.length; i++) {
581-
if (!innerDeepEqual(a[i], b[i], strict, memos)) {
554+
if (hasOwnProperty(a, i)) {
555+
if (!hasOwnProperty(b, i) ||
556+
!innerDeepEqual(a[i], b[i], strict, memos)) {
557+
return false;
558+
}
559+
} else if (hasOwnProperty(b, i)) {
582560
return false;
561+
} else {
562+
// Array is sparse.
563+
const keysA = objectKeys(a);
564+
i++;
565+
for (; i < keysA.length; i++) {
566+
const key = keysA[i];
567+
if (!hasOwnProperty(b, key) ||
568+
!innerDeepEqual(a[key], b[i], strict, memos)) {
569+
return false;
570+
}
571+
}
572+
if (keysA.length !== objectKeys(b).length) {
573+
return false;
574+
}
575+
return true;
583576
}
584577
}
585578
}

src/node_util.cc

+25
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,29 @@ using v8::Proxy;
1616
using v8::String;
1717
using v8::Value;
1818

19+
static void GetOwnNonIndexProperties(
20+
const FunctionCallbackInfo<Value>& args) {
21+
Environment* env = Environment::GetCurrent(args);
22+
Local<Context> context = env->context();
23+
24+
if (!args[0]->IsObject())
25+
return;
26+
27+
v8::Local<v8::Object> object = args[0].As<v8::Object>();
28+
29+
// Return only non-enumerable properties by default.
30+
v8::Local<v8::Array> properties;
31+
32+
if (!object->GetPropertyNames(
33+
context, v8::KeyCollectionMode::kOwnOnly,
34+
v8::ONLY_ENUMERABLE,
35+
v8::IndexFilter::kSkipIndices)
36+
.ToLocal(&properties)) {
37+
return;
38+
}
39+
args.GetReturnValue().Set(properties);
40+
}
41+
1942
static void GetPromiseDetails(const FunctionCallbackInfo<Value>& args) {
2043
// Return undefined if it's not a Promise.
2144
if (!args[0]->IsPromise())
@@ -177,6 +200,8 @@ void Initialize(Local<Object> target,
177200
env->SetMethodNoSideEffect(target, "getProxyDetails", GetProxyDetails);
178201
env->SetMethodNoSideEffect(target, "safeToString", SafeToString);
179202
env->SetMethodNoSideEffect(target, "previewEntries", PreviewEntries);
203+
env->SetMethodNoSideEffect(target, "getOwnNonIndexProperties",
204+
GetOwnNonIndexProperties);
180205

181206
env->SetMethod(target, "startSigintWatchdog", StartSigintWatchdog);
182207
env->SetMethod(target, "stopSigintWatchdog", StopSigintWatchdog);

test/parallel/test-assert-deep.js

+11
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,17 @@ assert.throws(() => assert.deepStrictEqual(new Boolean(true), {}),
934934
' [\n 1,\n- 2\n+ 2,\n+ 3\n ]' }
935935
);
936936
util.inspect.defaultOptions = tmp;
937+
938+
const invalidTrap = new Proxy([1, 2, 3], {
939+
ownKeys() { return []; }
940+
});
941+
assert.throws(
942+
() => assert.deepStrictEqual(invalidTrap, [1, 2, 3]),
943+
{
944+
name: 'TypeError',
945+
message: "'ownKeys' on proxy: trap result did not include 'length'"
946+
}
947+
);
937948
}
938949

939950
// Basic valueOf check.

0 commit comments

Comments
 (0)