Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

assert: improve partialDeepStrictEqual #57370

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

BridgeAR
Copy link
Member

@BridgeAR BridgeAR commented Mar 8, 2025

This significantly improves the assert.partialDeepStrictEqual
implementation by reusing the already existing algorithm.
It is significantly faster and handles edge cases like symbols
identical as the deepStrictEqual algorithm. This is crucial to
remove the experimental status from the implementation.

The old implementation could not yet handle some cases that
the benchmark handles. These are deactivated for now.
This is likely also a good idea due to the significant performance
difference between the two implementations.

I changed the stability index, since it's now handling all cases
properly besides the non-enumerable cause and errors
properties on errors. I guess that can land later. I would also
feel comfortable to change the stability to stable after this
lands.This significantly improves the assert.partialDeepStrictEqual
implementation by reusing the already existing algorithm.
It is significantly faster and handles edge cases like symbols
identical as the deepStrictEqual algorithm. This is crucial to
remove the experimental status from the implementation.

This includes a performance improvement for assert.deepStrictEqual().
A few uncommon cases are actually slower without me being
able to detect the reason for this difference. It's related to
object comparison.

I noticed an interesting thing while working on this.
ObjectPrototypeHasOwnProperty is significantly faster than
ObjectHasOwn. This is something we could take advantage
of in other parts of the code and I plan on opening an issue for
V8 to fix that.

BridgeAR added 2 commits March 8, 2025 03:13
This significantly improves the assert.partialDeepStrictEqual
implementation by reusing the already existing algorithm.
It is significantly faster and handles edge cases like symbols
identical as the deepStrictEqual algorithm. This is crucial to
remove the experimental status from the implementation.
The current settings deactivate the extraProps handling, due to
the current implementation failing on these cases.
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/performance

@nodejs-github-bot nodejs-github-bot added assert Issues and PRs related to the assert subsystem. needs-ci PRs that need a full CI run. util Issues and PRs related to the built-in util module. labels Mar 8, 2025
@BridgeAR BridgeAR added request-ci Add this label to start a Jenkins CI on a PR. needs-benchmark-ci PR that need a benchmark CI run. labels Mar 8, 2025
@BridgeAR
Copy link
Member Author

BridgeAR commented Mar 8, 2025

Benchmark https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1674/

Local results (only three stars)

                                                                                           confidence improvement accuracy (*)     (**)     (***)

assert/deepequal-map.js method='deepEqual_mixed' strict=0 len=500 n=2000                                                     ***      5.64 %       ±0.56%   ±0.74%    ±0.96%
assert/deepequal-map.js method='deepEqual_mixed' strict=1 len=500 n=2000                                                     ***      5.08 %       ±0.71%   ±0.95%    ±1.24%
assert/deepequal-map.js method='deepEqual_objectOnly' strict=0 len=500 n=2000                                                ***      6.22 %       ±0.75%   ±1.00%    ±1.31%
assert/deepequal-map.js method='deepEqual_objectOnly' strict=1 len=500 n=2000                                                ***      5.41 %       ±0.52%   ±0.70%    ±0.91%
assert/deepequal-map.js method='deepEqual_primitiveOnly' strict=1 len=500 n=2000                                             ***      3.20 %       ±1.82%   ±2.42%    ±3.15%
assert/deepequal-map.js method='notDeepEqual_objectOnly' strict=0 len=500 n=2000                                             ***    -21.55 %       ±1.69%   ±2.27%    ±3.01%
assert/deepequal-map.js method='notDeepEqual_objectOnly' strict=1 len=500 n=2000                                             ***    -19.48 %       ±0.34%   ±0.45%    ±0.59%
assert/deepequal-map.js method='notDeepEqual_primitiveOnly' strict=1 len=500 n=2000                                          ***      4.03 %       ±1.68%   ±2.24%    ±2.92%
assert/deepequal-object.js method='deepEqual' size=10000 n=50                                                                ***      3.47 %       ±0.50%   ±0.66%    ±0.86%
assert/deepequal-object.js method='deepStrictEqual' size=10000 n=50                                                          ***      2.98 %       ±0.61%   ±0.82%    ±1.07%
assert/deepequal-object.js method='notDeepEqual' size=10000 n=50                                                             ***     -6.07 %       ±3.20%   ±4.27%    ±5.58%
assert/deepequal-object.js method='notDeepStrictEqual' size=10000 n=50                                                       ***     -6.89 %       ±3.08%   ±4.10%    ±5.33%
assert/deepequal-prims-and-objs-big-loop.js method='deepEqual' strict=0 n=100000 primitive='array'                           ***     17.87 %       ±1.27%   ±1.70%    ±2.21%
assert/deepequal-prims-and-objs-big-loop.js method='deepEqual' strict=0 n=100000 primitive='set_object'                      ***      6.13 %       ±0.94%   ±1.25%    ±1.63%
assert/deepequal-prims-and-objs-big-loop.js method='deepEqual' strict=0 n=100000 primitive='set_simple'                      ***      6.90 %       ±1.88%   ±2.50%    ±3.25%
assert/deepequal-prims-and-objs-big-loop.js method='deepEqual' strict=1 n=100000 primitive='array'                           ***     16.31 %       ±0.81%   ±1.08%    ±1.41%
assert/deepequal-prims-and-objs-big-loop.js method='deepEqual' strict=1 n=100000 primitive='set_object'                      ***      3.36 %       ±0.74%   ±0.98%    ±1.28%
assert/deepequal-prims-and-objs-big-loop.js method='deepEqual' strict=1 n=100000 primitive='set_simple'                      ***      4.09 %       ±1.29%   ±1.71%    ±2.23%
assert/deepequal-prims-and-objs-big-loop.js method='notDeepEqual' strict=1 n=100000 primitive='array'                        ***     12.30 %       ±1.25%   ±1.67%    ±2.17%
assert/deepequal-prims-and-objs-big-loop.js method='notDeepEqual' strict=1 n=100000 primitive='circular'                     ***      6.07 %       ±0.88%   ±1.17%    ±1.53%
assert/deepequal-prims-and-objs-big-loop.js method='notDeepEqual' strict=1 n=100000 primitive='set_object'                   ***      4.00 %       ±0.98%   ±1.30%    ±1.69%
assert/deepequal-prims-and-objs-big-loop.js method='notDeepEqual' strict=1 n=100000 primitive='set_simple'                   ***      3.07 %       ±1.39%   ±1.86%    ±2.43%
assert/deepequal-set.js method='deepEqual_mixed' strict=0 len=500 n=1000                                                     ***      6.32 %       ±0.83%   ±1.11%    ±1.46%
assert/deepequal-set.js method='deepEqual_mixed' strict=1 len=500 n=1000                                                     ***      5.49 %       ±0.79%   ±1.05%    ±1.37%
assert/deepequal-set.js method='deepEqual_objectOnly' strict=0 len=500 n=1000                                                ***      5.54 %       ±0.67%   ±0.90%    ±1.17%
assert/deepequal-set.js method='deepEqual_objectOnly' strict=1 len=500 n=1000                                                ***     13.51 %       ±0.58%   ±0.77%    ±1.00%
assert/deepequal-set.js method='notDeepEqual_mixed' strict=0 len=500 n=1000                                                  ***     -5.19 %       ±2.72%   ±3.61%    ±4.70%
assert/deepequal-set.js method='notDeepEqual_mixed' strict=1 len=500 n=1000                                                  ***     -6.64 %       ±3.42%   ±4.55%    ±5.92%
assert/deepequal-set.js method='notDeepEqual_objectOnly' strict=0 len=500 n=1000                                             ***    -20.44 %       ±0.48%   ±0.64%    ±0.84%
assert/deepequal-set.js method='notDeepEqual_objectOnly' strict=1 len=500 n=1000                                             ***    -14.00 %       ±0.63%   ±0.84%    ±1.09%
assert/deepequal-simple-array-and-set.js method='deepEqual_Array' strict=1 len=10000 n=1000                                  ***    164.03 %       ±1.76%   ±2.35%    ±3.06%
assert/deepequal-simple-array-and-set.js method='notDeepEqual_Array' strict=1 len=10000 n=1000                               ***    166.87 %       ±1.28%   ±1.70%    ±2.23%
assert/deepequal-simple-array-and-set.js method='notDeepEqual_Set' strict=1 len=10000 n=1000                                 ***     45.39 %       ±3.95%   ±5.25%    ±6.83%
assert/partial-deep-equal.js datasetName='array' extraProps=0 size=500 n=25                                                  ***  48100.43 %     ±723.31% ±974.81% ±1294.17%
assert/partial-deep-equal.js datasetName='arrayBuffers' extraProps=0 size=500 n=25                                           ***    132.61 %       ±5.27%   ±7.09%    ±9.39%
assert/partial-deep-equal.js datasetName='circularRefs' extraProps=0 size=500 n=25                                           ***   2996.48 %      ±17.17%  ±23.15%   ±30.73%
assert/partial-deep-equal.js datasetName='dataViewArrayBuffers' extraProps=0 size=500 n=25                                   ***    141.24 %       ±4.56%   ±6.12%    ±8.08%
assert/partial-deep-equal.js datasetName='maps' extraProps=0 size=500 n=25                                                   ***   2094.33 %      ±14.30%  ±19.27%   ±25.59%
assert/partial-deep-equal.js datasetName='objects' extraProps=0 size=500 n=25                                                ***   7124.44 %      ±49.25%  ±66.37%   ±88.11%
assert/partial-deep-equal.js datasetName='sets' extraProps=0 size=500 n=25                                                   ***   1438.30 %      ±10.41%  ±14.03%   ±18.63%
assert/partial-deep-equal.js datasetName='typedArrays' extraProps=0 size=500 n=25                                            ***  14175.77 %     ±102.39% ±137.99%  ±183.20%

@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Mar 8, 2025
@nodejs-github-bot
Copy link
Collaborator

Copy link

codecov bot commented Mar 8, 2025

Codecov Report

Attention: Patch coverage is 99.68454% with 1 line in your changes missing coverage. Please review.

Project coverage is 90.22%. Comparing base (15fec13) to head (bc255c4).
Report is 140 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/util/comparisons.js 99.68% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #57370      +/-   ##
==========================================
+ Coverage   89.02%   90.22%   +1.20%     
==========================================
  Files         665      630      -35     
  Lines      193408   185070    -8338     
  Branches    37275    36226    -1049     
==========================================
- Hits       172180   166981    -5199     
+ Misses      13891    11056    -2835     
+ Partials     7337     7033     -304     
Files with missing lines Coverage Δ
lib/assert.js 99.52% <100.00%> (+0.47%) ⬆️
lib/internal/util/comparisons.js 99.76% <99.68%> (-0.24%) ⬇️

... and 160 files with indirect coverage changes

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

BridgeAR added 5 commits March 8, 2025 15:31
These benchmarks are not frequently needed and just slow down the
default benchmark suite. They are kept for users who want to run
them but deactivated by default.
This fixes holey array handling. These were a bit more tricky with
the partial implementation.

It also adds multiple tests and makes sure the implementation
always checks for being a drop-in implementation for
assert.deepStrictEqual()
Each file should have a reasonable runtime while having a good
accuracy. This adjust those up and down to have minimal runtimes
with a good accuracy.
This improves the performance for array comparison by making the
sparse array detection simpler. On top of that it adds a fast path
for sets and maps that only contain objects as key.
@BridgeAR BridgeAR requested a review from RafaelGSS March 8, 2025 17:19
@@ -245,6 +291,7 @@ function innerDeepEqual(val1, val2, strict, memos) {

const message1Enumerable = ObjectPrototypePropertyIsEnumerable(val1, 'message');
const name1Enumerable = ObjectPrototypePropertyIsEnumerable(val1, 'name');
// TODO(BridgeAR): Adjust cause and errors properties for partial mode.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be resolved before we call the implementation RC?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have removed the experimental warning with it applied :)
I can't think of more cases needing further handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
assert Issues and PRs related to the assert subsystem. needs-benchmark-ci PR that need a benchmark CI run. needs-ci PRs that need a full CI run. util Issues and PRs related to the built-in util module.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants