Skip to content

process: use a class for nextTick resources - #66415

Open
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:process-tickobject-class
Open

mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:process-tickobject-class

Conversation

@mcollina

@mcollina mcollina commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

The resource created for each process.nextTick() call was an object literal with three computed symbol keys. V8 builds such literals from a boilerplate that only covers the static keys (callback, args), so the three symbol-keyed properties end up in a separate PropertyArray allocated per tick, and each computed-key store goes through the runtime.

Constructing the resource through a class gives every tick object one map with all five fields in-object (64 bytes instead of 56 + 40), and plain monomorphic stores.

benchmark/compare.js --runs 10 --filter next-tick process:

                                                confidence improvement accuracy (*)    (**)   (***)
process/next-tick-breadth-args.js n=10000000             *     -7.93 %       ±5.93%  ±8.39% ±12.04%
process/next-tick-breadth.js n=10000000                 **     -8.13 %       ±4.80%  ±6.74%  ±9.52%
process/next-tick-depth-args.js n=7000000              ***      7.27 %       ±3.76%  ±5.16%  ±7.06%
process/next-tick-depth.js n=7000000                    **      5.16 %       ±3.18%  ±4.39%  ±6.04%
process/next-tick-exec-args.js n=4000000                       -8.27 %      ±27.63% ±38.30% ±53.18%
process/next-tick-exec.js n=4000000                             0.04 %       ±4.39%  ±6.10%  ±8.51%
process/next-tick-loop-args.js loop=100 n=10000        ***     35.46 %       ±3.59%  ±4.96%  ±6.85%
process/next-tick-loop-args.js loop=100 n=20000        ***     27.13 %       ±2.95%  ±4.05%  ±5.54%
process/next-tick-loop-args.js loop=100 n=40000                 4.65 %       ±7.27% ±10.00% ±13.71%
process/next-tick-loop-args.js loop=200 n=10000        ***     23.76 %       ±3.57%  ±4.91%  ±6.75%
process/next-tick-loop-args.js loop=200 n=20000        ***     17.34 %       ±3.15%  ±4.31%  ±5.88%
process/next-tick-loop-args.js loop=200 n=40000                 4.90 %       ±6.92%  ±9.71% ±13.73%
process/next-tick-loop.js loop=100 n=10000             ***     33.84 %       ±4.55%  ±6.42%  ±9.16%
process/next-tick-loop.js loop=100 n=20000             ***     22.64 %       ±2.73%  ±3.75%  ±5.11%
process/next-tick-loop.js loop=100 n=40000             ***      9.00 %       ±2.85%  ±3.91%  ±5.35%
process/next-tick-loop.js loop=200 n=10000             ***     22.81 %       ±2.92%  ±4.01%  ±5.49%
process/next-tick-loop.js loop=200 n=20000             ***     13.24 %       ±2.80%  ±3.84%  ±5.23%
process/next-tick-loop.js loop=200 n=40000             ***      8.20 %       ±2.88%  ±3.95%  ±5.40%

The two breadth benchmarks queue 10M ticks before draining anything, so all tick objects stay alive and the run is dominated by scavenges copying them (2–3 s of GC in a 3–5 s run). On main the literal has an allocation site, and in some runs V8's allocation-site feedback pretenures the tick objects straight into old space: scavenges drop from ~85 to ~9 and the rate jumps from ~1.8M to ~3.5M ops/s. Class instances get no allocation-site feedback, so this PR sits at a steady ~2.4–3.5M with ~40–70 scavenges: faster than main's common regime, slower than its pretenured one, hence the noisy −8% average. Real workloads drain ticks the same turn they are scheduled, where pretenuring would be a pessimization; on an HTTP hello-world server this saves ~130 bytes per request (4 ticks) with no throughput change.

🤖 Generated with Claude Code

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. process Issues and PRs related to the process subsystem. labels Sep 30, 2026
The resource created for each `process.nextTick()` call was an object
literal with three computed symbol keys. V8 builds such literals from a
boilerplate that only covers the static keys, so the symbol-keyed
properties end up in a separate property array allocated per tick, and
each computed-key store goes through the runtime.

Construct the resource through a class instead, so that every tick
object shares one map with all five fields in-object.

process/next-tick-breadth-args.js: ~1.58M -> ~1.95M ops/s (+23%).

Signed-off-by: Matteo Collina <hello@matteocollina.com>
@mcollina
mcollina force-pushed the process-tickobject-class branch from a5677ca to d88d4a2 Compare September 30, 2026 14:38
@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.37%. Comparing base (8bf7793) to head (d88d4a2).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #66415      +/-   ##
==========================================
- Coverage   90.39%   90.37%   -0.02%     
==========================================
  Files         792      792              
  Lines      275580   275690     +110     
  Branches    52840    52858      +18     
==========================================
+ Hits       249104   249158      +54     
- Misses      16897    16938      +41     
- Partials     9579     9594      +15     
Files with missing lines Coverage Δ
lib/internal/process/task_queues.js 100.00% <100.00%> (ø)

... and 41 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.

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Benchmark GHA (process / next-tick): https://github.com/nodejs/node/actions/runs/36746480545

Results

[!WARNING]
Do not take GHA benchmark results as face value, always confirm them
using a dedicated machine, e.g. Jenkins CI.

Benchmark results:

                                                confidence improvement accuracy (*)    (**)   (***)
process/next-tick-breadth-args.js n=10000000                   -9.80 %      ±10.94% ±14.43% ±18.52%
process/next-tick-breadth.js n=10000000                ***    -17.11 %       ±7.64% ±10.07% ±12.93%
process/next-tick-depth-args.js n=7000000                       3.88 %       ±9.46% ±12.47% ±16.00%
process/next-tick-depth.js n=7000000                            8.63 %       ±8.81% ±11.61% ±14.89%
process/next-tick-exec-args.js n=4000000               ***     48.11 %      ±13.06% ±17.23% ±22.12%
process/next-tick-exec.js n=4000000                            -1.11 %       ±8.07% ±10.64% ±13.66%
process/next-tick-loop-args.js loop=100 n=10000        ***     34.70 %      ±10.59% ±13.96% ±17.92%
process/next-tick-loop-args.js loop=100 n=20000        ***     25.26 %      ±10.33% ±13.62% ±17.49%
process/next-tick-loop-args.js loop=100 n=40000                 6.22 %       ±9.51% ±12.54% ±16.09%
process/next-tick-loop-args.js loop=200 n=10000        ***     23.01 %      ±10.18% ±13.42% ±17.22%
process/next-tick-loop-args.js loop=200 n=20000         **     15.74 %       ±9.80% ±12.91% ±16.57%
process/next-tick-loop-args.js loop=200 n=40000                 6.73 %       ±9.61% ±12.66% ±16.25%
process/next-tick-loop.js loop=100 n=10000             ***     35.69 %       ±9.85% ±12.99% ±16.68%
process/next-tick-loop.js loop=100 n=20000             ***     19.76 %       ±9.13% ±12.03% ±15.44%
process/next-tick-loop.js loop=100 n=40000               *      9.39 %       ±8.78% ±11.58% ±14.86%
process/next-tick-loop.js loop=200 n=10000             ***     20.52 %       ±9.21% ±12.14% ±15.58%
process/next-tick-loop.js loop=200 n=20000               *     11.49 %       ±8.88% ±11.70% ±15.02%
process/next-tick-loop.js loop=200 n=40000                      6.23 %       ±8.62% ±11.36% ±14.57%

Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 18 comparisons, you can thus
expect the following amount of false-positive results:
  0.90 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.18 false positives, when considering a   1% risk acceptance (**, ***),
  0.02 false positives, when considering a 0.1% risk acceptance (***)

[!WARNING]
Do not take GHA benchmark results as face value, always confirm them
using a dedicated machine, e.g. Jenkins CI.

@addaleax addaleax added the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Sep 30, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Sep 30, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. process Issues and PRs related to the process subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants