Skip to content

src: set UV_THREADPOOL_SIZE based on available parallelism#61533

Open
RafaelGSS wants to merge 2 commits intonodejs:mainfrom
RafaelGSS:add-dynamic-uv-threadpool-size
Open

src: set UV_THREADPOOL_SIZE based on available parallelism#61533
RafaelGSS wants to merge 2 commits intonodejs:mainfrom
RafaelGSS:add-dynamic-uv-threadpool-size

Conversation

@RafaelGSS
Copy link
Member

@RafaelGSS RafaelGSS commented Jan 26, 2026

When UV_THREADPOOL_SIZE is not set, Node.js will auto-size it based on uv_available_parallelism(), with a minimum of 4 and a maximum of 1024.

Refs: nodejs/performance#193

image

I will still run some benchmarks to see its real impact. Adding blocked label for now.

cc: @ronag

When UV_THREADPOOL_SIZE is not set, Node.js will auto-size it based on
uv_available_parallelism(), with a minimum of 4 and a maximum of 1024.
@RafaelGSS RafaelGSS added semver-major PRs that contain breaking changes and should be released in the next major version. blocked PRs that are blocked by other issues or PRs. notable-change PRs with changes that should be highlighted in changelogs. labels Jan 26, 2026
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/startup

@github-actions
Copy link
Contributor

The notable-change PRs with changes that should be highlighted in changelogs. label has been added by @RafaelGSS.

Please suggest a text for the release notes if you'd like to include a more detailed summary, then proceed to update the PR description with the text or a link to the notable change suggested text comment. Otherwise, the commit will be placed in the Other Notable Changes section.

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Jan 26, 2026
@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.77%. Comparing base (644ba1f) to head (ed510c8).
⚠️ Report is 22 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #61533   +/-   ##
=======================================
  Coverage   89.76%   89.77%           
=======================================
  Files         672      672           
  Lines      203809   203817    +8     
  Branches    39189    39188    -1     
=======================================
+ Hits       182949   182972   +23     
+ Misses      13179    13168   -11     
+ Partials     7681     7677    -4     
Files with missing lines Coverage Δ
src/node.cc 76.33% <100.00%> (+0.25%) ⬆️

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

Copy link
Member

@juanarbol juanarbol left a comment

Choose a reason for hiding this comment

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

lgtm

Copy link
Member

@ronag ronag left a comment

Choose a reason for hiding this comment

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

LGTM.

Though I think there is some confusion here regarding parallelism vs concurrency. It can be meaningful to have more io dispatch threads than logical cpus. The task of the uv thread pool (in the case of fs) is to dispatch io and wait for results. So if your hardware can do 128 io requests in parallel you only need to dispatch 128 io requests concurrently which you can do on e.g. 16 logical cpus / available parallelism as well. In the opposite end, if your io hardware only has 128 parallel requests, than having more than 128 worker threads is not meaningful.

The whole thing with the "right" number of threads is super complicated with the current architecture. If we used ioring instead for fs then available parallelism would be the perfect amount.

That being said, I think available parallelism is a good guess but far from being a guaranteed optimal.

int rc = uv_os_getenv("UV_THREADPOOL_SIZE", buf, &buf_size);
if (rc == UV_ENOENT) {
unsigned int parallelism = uv_available_parallelism();
unsigned int threadpool_size = std::min(std::max(4u, parallelism), 1024u);
Copy link
Member

Choose a reason for hiding this comment

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

Is it worth just deferring the maximum to libuv here, since it already caps to MAX_THREADPOOL_SIZE, rather than hardcoding it a second time?

Copy link
Member Author

Choose a reason for hiding this comment

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

If libuv changes that in a semver-minor version, it would affect Node.js. We could defer to them, but having a lower number in the MAX_THREADPOOL_SIZE sounds "safer".

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, it's better to rely on the MAX_THREADPOOL_SIZE const in libuv, coz Node as a whole depends entirely on libuv for io/parallelism, and this number can only go up ⬆️ anyway, so there's nothing to worry about from a safety perspective 🦺

Copy link
Contributor

@Ethan-Arrowood Ethan-Arrowood left a comment

Choose a reason for hiding this comment

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

I'm generally in favor of this change. Though I appreciate more research on if this is better for overall performance. I did something similar for picking a more sensible Node.js test_runner parallelization number for resource intensive tests. You're welcome to evaluate/use/reference my process and code here: https://github.com/Ethan-Arrowood/node-test-runner-parallelization-analysis

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm

@RafaelGSS RafaelGSS added the performance Issues and PRs related to the performance of Node.js. label Jan 28, 2026
Copy link
Member

@gurgunday gurgunday left a comment

Choose a reason for hiding this comment

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

lgtm

@Flarna
Copy link
Member

Flarna commented Feb 1, 2026

Which overhead (e.g. memory) is expected per uv thread?
I could imagine it's not negligible if number goes from 4 to 1024.

@bricss
Copy link
Contributor

bricss commented Feb 2, 2026

Note: Under most optimal conditions, the value 🔢 shall never exceed the following formula:
export UV_THREADPOOL_SIZE=$(($(nproc) * 2))

@RafaelGSS
Copy link
Member Author

RafaelGSS commented Feb 6, 2026

I took some time today, and I did the following measurement:

Benchmark Results: PR vs Node.js v24.13.0

This report compares the performance of the PR Build against the Baseline (v24.13.0) across three machine configurations: 2GB, 8GB, and 32GB.

Machine Configurations (lscpu)

Machine CPUs Model RAM Notes
2GB 2 AMD EPYC-Rome @ 2.0GHz 2GB Small instance
8GB 4 AMD EPYC-Rome @ 2.0GHz 8GB Medium instance
32GB 16 AMD EPYC-Rome @ 2.0GHz 32GB Large instance

1. 32GB Machine (16 vCPUs)

PR Threadpool: 16 | Baseline Threadpool: 4

Endpoint Metric PR Baseline Difference
/crypto Req/Sec 466,679 122,350 +281.42%
Avg Latency 35.71 ms 137.34 ms -73.99%
/fs Req/Sec 1,548,970 1,830,365 -15.37%
Avg Latency 10.41 ms 8.74 ms +19.10%
/mixed Req/Sec 1,230,062 651,400 +88.83%
Avg Latency 13.25 ms 25.45 ms -47.93%

2. 8GB Machine (4 vCPUs)

PR Threadpool: 4 | Baseline Threadpool: 4

Endpoint Metric PR Baseline Difference
/crypto Req/Sec 100,970 100,859 +0.11%
/fs Req/Sec 1,439,445 1,480,260 -2.76%
/mixed Req/Sec 495,552 511,667 -3.15%

3. 2GB Machine (2 vCPUs)

PR Threadpool: 4 | Baseline Threadpool: 4*

Endpoint Metric PR Baseline Difference
/crypto Req/Sec 59,074 59,651 -0.97%
/fs Req/Sec 1,836,680 1,892,625 -2.96%
/mixed Req/Sec 337,245 349,286 -3.45%

If you have dedicated machines, feel free to run: curl -sL https://raw-githubusercontent-com-gh.computerqwq.top/RafaelGSS/uv-threadpool-benchmark/main/benchmark.sh | bash

@ronag
Copy link
Member

ronag commented Feb 8, 2026

Didn't go so swell:


============================================================
SUMMARY
============================================================

Route: /crypto
  Requests:       0
  Throughput:     0.00 req/sec
  Latency (avg):  0.00 ms
  Latency (p99):  0.00 ms
  Errors:         1380543
  Timeouts:       0

Route: /fs
  Requests:       0
  Throughput:     0.00 req/sec
  Latency (avg):  0.00 ms
  Latency (p99):  0.00 ms
  Errors:         1369912
  Timeouts:       0

Route: /mixed
  Requests:       0
  Throughput:     0.00 req/sec
  Latency (avg):  0.00 ms
  Latency (p99):  0.00 ms
  Errors:         1366417
  Timeouts:       0
Failed to fetch metrics: fetch failed


Stopping server...
bash: line 81: kill: (1658) - No such process

@RafaelGSS
Copy link
Member Author

Which OS? @ronag

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

Labels

blocked PRs that are blocked by other issues or PRs. c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. notable-change PRs with changes that should be highlighted in changelogs. performance Issues and PRs related to the performance of Node.js. semver-major PRs that contain breaking changes and should be released in the next major version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants