Skip to content

Worker pool defaults to logical CPUs, not physical cores: ~2x the CPU on SMT hosts #606

Description

@singaraiona

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:

  1. The suite on a 16+ physical-core SMT host, logical vs physical, comparing
    per-query hot times.
  2. 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).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions