assert: improve deepEqual perf for large input#12849
assert: improve deepEqual perf for large input#12849addaleax wants to merge 1 commit intonodejs:masterfrom
Conversation
|
Also, I found some weird behaviour while doing this: a = {};
assert.deepStrictEqual([a,a], [a,{}]);This does not throw, but I think it should; do you agree? Would you consider changing it semver-major? If not, I could do it in this PR, otherwise I’ll just wait until after this to avoid conflicts. |
lib/assert.js
Outdated
There was a problem hiding this comment.
How about instead doing if (!memos) { memos = ... }?
lib/assert.js
Outdated
lib/assert.js
Outdated
There was a problem hiding this comment.
!memos.expected.map.has(expected)?
776351a to
788ac8b
Compare
7664b48 to
a60f489
Compare
lib/assert.js
Outdated
There was a problem hiding this comment.
@joyeecheung That thought occurred to me and I couldn’t really find a difference … would there be one? Is that going to be more performant?
There was a problem hiding this comment.
@addaleax I think the GC pressure would be smaller after we leave the function because the references don't have be traversed?
|
@addaleax I keep being surprised by your energy and dedication 🥇 |
|
@refack That seems like a completely different issue, though? :D |
|
It fit under the title "assert: improve deepEqual perf for large input". No need to sort keys of arrays. It is an O(n*log(n)) operation. |
|
@refack Arrays can have extra non-indexed properties, too ;) This might not be as easy as you think. If you think you can improve the situation, feel free to experiment and see if you can get a PR together, and I’ll be happy to review. |
|
Challenge accepted 🖖 |
Imo, no. |
Fishrock123
left a comment
There was a problem hiding this comment.
CI again, since the last one only ran linux(?): https://ci.nodejs.org/job/node-test-pull-request/7899/
|
@joyeecheung Looks like doing this with |
Sorry, huh – what do memory addresses have to do with this? It’s not about the left and the right side of To have a more explicit example: a = {}; b = {};
assert.deepStrictEqual({key1:a,key2:a},{key1:a,key2:b});should throw imo, because |
lib/assert.js
Outdated
There was a problem hiding this comment.
Changing from Array.prototype.indexOf (which uses Strict Equality Comparison) to Map.prototype.get (which uses SameValueZero) can have subtle nuances that might result in incompatibilities, especially around NaN. Not sure if we need to care about it though.
There was a problem hiding this comment.
I don’t think that actually affects the logic here – if it matters at all, it should only affect cases where non-primitives compare deep-equal to primitives, but that doesn’t seem to happen anyway (e.g. assert.deepEqual([0], 0) throws even though [0] == 0).
One more reason to consider the implementation of IMO, that should not throw (so the current behavior is correct). The So, confusingly, the "strict" part doesn't apply to objects (or arrays or--ideally at least--Maps or Sets). It does mean "don't cast a string to a number" though. Whee! But I do agree it's confusing and unfortunate. Even if you disagree, I definitely wouldn't fix it in this PR because I predict a CITGM run will not be kind to the additional change. |
|
CI (linux only): https://ci.nodejs.org/job/node-test-commit-linux/9688/ |
|
@addaleax hmm wait, do we even need to put primitives into the memos? The memos are for cycles and is it even possible to make a cycle with primitives? (Or do we even care if they do since they are not compared recursively?) Also FWIW I think the example above should not throw because objects are not primitives and as the docs and the comments stare, for non-primitives we don't compare them by using operators. Objects with the same properties(or no properties), prototypes and tags should be considered equal. |
I don’t think so, for both questions – but I’m not sure it’s trivial to undo that without changing the logic here for really weird edge cases. |
Use a Map instead of an array for checking previously found cyclic references. This reduces complexity for an array-of-objects case from O(n²) to O(n·log n). Fixes: nodejs#12842 PR-URL: nodejs#12849 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
32c658d to
bc62437
Compare
|
Landed in 7e5f500 |
Use a Map instead of an array for checking previously found cyclic references. This reduces complexity for an array-of-objects case from O(n²) to O(n·log n). Fixes: #12842 PR-URL: #12849 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
Use a Map instead of an array for checking previously found cyclic references. This reduces complexity for an array-of-objects case from O(n²) to O(n·log n). Fixes: nodejs#12842 PR-URL: nodejs#12849 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
Use a Map instead of an array for checking previously found
cyclic references.
This reduces complexity for an array-of-objects case from
O(n²) to O(n·log n).
Case from the issue, new output:
Fixes: #12842
Checklist
make -j4 test(UNIX), orvcbuild test(Windows) passesAffected core subsystem(s)