Skip to content

Commit 21ea9bc

Browse files
committed
src: set UV_THREADPOOL_SIZE based on available parallelism
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.
1 parent 644ba1f commit 21ea9bc

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

doc/api/cli.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4051,6 +4051,15 @@ Wed May 12 2021 20:30:48 GMT+0100 (Irish Standard Time)
40514051

40524052
### `UV_THREADPOOL_SIZE=size`
40534053

4054+
<!-- YAML
4055+
changes:
4056+
- version: REPLACEME
4057+
pr-url: https://github.com/nodejs/node/pull/XXXX
4058+
description: Node.js now automatically sets `UV_THREADPOOL_SIZE` to the
4059+
available CPU parallelism (with a minimum of 4 and a maximum
4060+
of 1024) when the environment variable is not already set.
4061+
-->
4062+
40544063
Set the number of threads used in libuv's threadpool to `size` threads.
40554064

40564065
Asynchronous system APIs are used by Node.js whenever possible, but where they
@@ -4064,15 +4073,22 @@ on synchronous system APIs. Node.js APIs that use the threadpool are:
40644073
* `dns.lookup()`
40654074
* all `zlib` APIs, other than those that are explicitly synchronous
40664075

4076+
If this environment variable is not set, Node.js automatically sizes the
4077+
threadpool based on the available CPU parallelism, using a minimum of `4`
4078+
threads and a maximum of `1024`. This ensures better default performance on
4079+
machines with many CPU cores, where the previous fixed default of `4` threads
4080+
could become a bottleneck.
4081+
40674082
Because libuv's threadpool has a fixed size, it means that if for whatever
40684083
reason any of these APIs takes a long time, other (seemingly unrelated) APIs
40694084
that run in libuv's threadpool will experience degraded performance. In order to
40704085
mitigate this issue, one potential solution is to increase the size of libuv's
40714086
threadpool by setting the `'UV_THREADPOOL_SIZE'` environment variable to a value
4072-
greater than `4` (its current default value). However, setting this from inside
4073-
the process using `process.env.UV_THREADPOOL_SIZE=size` is not guranteed to work
4074-
as the threadpool would have been created as part of the runtime initialisation
4075-
much before user code is run. For more information, see the [libuv threadpool documentation][].
4087+
greater than the automatically computed default. However, setting this from
4088+
inside the process using `process.env.UV_THREADPOOL_SIZE=size` is not guaranteed
4089+
to work as the threadpool would have been created as part of the runtime
4090+
initialisation much before user code is run. For more information, see the
4091+
[libuv threadpool documentation][].
40764092

40774093
## Useful V8 options
40784094

src/node.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,24 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
12331233
#endif // HAVE_OPENSSL
12341234
}
12351235

1236+
// Set UV_THREADPOOL_SIZE based on available parallelism if not already set
1237+
// by the user. The libuv threadpool defaults to 4 threads, which can be
1238+
// suboptimal on machines with many CPU cores. Use uv_available_parallelism()
1239+
// as a heuristic, with a minimum of 4 (the previous default) and a maximum
1240+
// of 1024 (libuv's upper bound).
1241+
{
1242+
char buf[64];
1243+
size_t buf_size = sizeof(buf);
1244+
int rc = uv_os_getenv("UV_THREADPOOL_SIZE", buf, &buf_size);
1245+
if (rc == UV_ENOENT) {
1246+
unsigned int parallelism = uv_available_parallelism();
1247+
unsigned int threadpool_size = std::min(std::max(4u, parallelism), 1024u);
1248+
char size_str[16];
1249+
snprintf(size_str, sizeof(size_str), "%u", threadpool_size);
1250+
uv_os_setenv("UV_THREADPOOL_SIZE", size_str);
1251+
}
1252+
}
1253+
12361254
if (!(flags & ProcessInitializationFlags::kNoInitializeNodeV8Platform)) {
12371255
uv_thread_setname("node-MainThread");
12381256
per_process::v8_platform.Initialize(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
const { spawnSyncAndAssert } = require('../common/child_process');
3+
const assert = require('assert');
4+
const os = require('os');
5+
6+
const expectedSize = Math.min(Math.max(4, os.availableParallelism()), 1024);
7+
8+
// When UV_THREADPOOL_SIZE is not set, Node.js should auto-size it based on
9+
// uv_available_parallelism(), with a minimum of 4 and a maximum of 1024.
10+
{
11+
const env = { ...process.env };
12+
delete env.UV_THREADPOOL_SIZE;
13+
14+
spawnSyncAndAssert(
15+
process.execPath,
16+
['-e', 'console.log(process.env.UV_THREADPOOL_SIZE)'],
17+
{ env },
18+
{
19+
stdout(output) {
20+
assert.strictEqual(output.trim(), String(expectedSize));
21+
},
22+
},
23+
);
24+
}
25+
26+
// When UV_THREADPOOL_SIZE is explicitly set, Node.js should not override it.
27+
{
28+
const env = { ...process.env, UV_THREADPOOL_SIZE: '8' };
29+
30+
spawnSyncAndAssert(
31+
process.execPath,
32+
['-e', 'console.log(process.env.UV_THREADPOOL_SIZE)'],
33+
{ env },
34+
{
35+
stdout(output) {
36+
assert.strictEqual(output.trim(), '8');
37+
},
38+
},
39+
);
40+
}

0 commit comments

Comments
 (0)