Summary
The worker pool sizes itself to every online logical CPU. On any SMT machine
that recruits hyperthread siblings, which cost CPU and buy little or no wall
time. ray_physical_core_count() already exists for exactly this reason and is
never used to size the pool.
src/core/pool.c:
/* Default to every online logical CPU. Individual operations may
* bound their task count to their workload or memory budget. */
uint32_t ncpu = ray_thread_count(); /* sysconf(_SC_NPROCESSORS_ONLN) */
n_workers = (ncpu > 1) ? ncpu - 1 : 0;
src/core/platform.c carries the counter-argument in its own comment:
Physical cores (SMT siblings collapsed). The worker pool's kernels are
memory-bound; two hyperthreads sharing one core's load/store machinery only
add contention (measured: the full ClickBench suite runs ~11% SLOWER with 32
SMT threads than with the 16 physical cores on a 5950X).
ray_physical_core_count() is called in exactly one place: estimating a cache
size.
Measurements
Changing that one call and nothing else. i7-6700, 4 physical / 8 logical,
release build.
The join shape from #599 (130k-row probe against a 3-row key set, ×200):
|
user CPU |
wall |
| logical (8 workers) |
5.19 s |
0.70 s |
| physical (4 workers) |
2.58 s (−50%) |
0.66 s |
Same workload at 50k rows: 2.07 s → 1.16 s CPU (−44%), wall 0.36 s → 0.35 s.
Half the CPU, no wall time given up.
ClickBench, 10M rows, splayed via .db.splayed.get, 43 queries × 3 runs:
|
sum of per-query hot times |
process CPU |
process wall |
| logical |
5690.7 ms |
55.1 s |
23.3 s |
| physical |
5845.6 ms (+2.7%) |
36.4 s (−34%) |
18.6 s (−20%) |
The two metrics disagree and that is the crux. Whole-process CPU and wall both
improve substantially, but the per-query hot times — the numbers that get
published — regress 2.7% on average, with 12 queries over 10% and a worst case
of +36%:
q25 10.50 -> 14.27 ms +35.9%
q26 5.95 -> 7.95 ms +33.7%
q24 6.12 -> 7.97 ms +30.2%
q9 69.18 -> 85.96 ms +24.2%
q20 41.77 -> 50.64 ms +21.2%
q27 108.97 -> 130.76 ms +20.0%
Why this needs a decision rather than a patch
This box is the least favourable case for the change: 4 physical / 8 logical
means surrendering half the threads. The in-tree 5950X measurement (16 physical /
32 logical) points the other way — 11% faster on physical cores. So the +2.7%
here may well be a small-machine artifact, and on the hosts the published numbers
come from the change may be a straight win on both axes.
I cannot resolve that from one machine. What would settle it:
- The suite on a 16+ physical-core SMT host, logical vs physical, comparing
per-query hot times.
- A check on whether any regressed query is SMT-friendly for a specific reason
(latency-bound pointer chasing rather than bandwidth-bound scanning), which
would argue for a per-operator decision instead of a global default.
Why it matters
Every non-latency-bound deployment silently pays roughly double the CPU it
needs — see #599, where a production service ran at 50% CPU against 25% for an
identical instance with -c 2 on the same feed, in lock-step on throughput.
-c is the workaround today, but it requires the operator to know their physical
core count and to know that this is a thing to know.
Related: #599 (the report this came out of), #605 (wake only the workers a
dispatch can keep busy — independent, and not a fix for either).
Summary
The worker pool sizes itself to every online logical CPU. On any SMT machine
that recruits hyperthread siblings, which cost CPU and buy little or no wall
time.
ray_physical_core_count()already exists for exactly this reason and isnever used to size the pool.
src/core/pool.c:src/core/platform.ccarries the counter-argument in its own comment:ray_physical_core_count()is called in exactly one place: estimating a cachesize.
Measurements
Changing that one call and nothing else. i7-6700, 4 physical / 8 logical,
release build.
The join shape from #599 (130k-row probe against a 3-row key set, ×200):
Same workload at 50k rows: 2.07 s → 1.16 s CPU (−44%), wall 0.36 s → 0.35 s.
Half the CPU, no wall time given up.
ClickBench, 10M rows, splayed via
.db.splayed.get, 43 queries × 3 runs:The two metrics disagree and that is the crux. Whole-process CPU and wall both
improve substantially, but the per-query hot times — the numbers that get
published — regress 2.7% on average, with 12 queries over 10% and a worst case
of +36%:
Why this needs a decision rather than a patch
This box is the least favourable case for the change: 4 physical / 8 logical
means surrendering half the threads. The in-tree 5950X measurement (16 physical /
32 logical) points the other way — 11% faster on physical cores. So the +2.7%
here may well be a small-machine artifact, and on the hosts the published numbers
come from the change may be a straight win on both axes.
I cannot resolve that from one machine. What would settle it:
per-query hot times.
(latency-bound pointer chasing rather than bandwidth-bound scanning), which
would argue for a per-operator decision instead of a global default.
Why it matters
Every non-latency-bound deployment silently pays roughly double the CPU it
needs — see #599, where a production service ran at 50% CPU against 25% for an
identical instance with
-c 2on the same feed, in lock-step on throughput.-cis the workaround today, but it requires the operator to know their physicalcore count and to know that this is a thing to know.
Related: #599 (the report this came out of), #605 (wake only the workers a
dispatch can keep busy — independent, and not a fix for either).