Architecture
Topology in, locality out.
numaperf is a workspace of focused crates layered on one runtime topology model. Discover the machine once, then place memory, pin threads, and schedule work against the same source of truth — and measure whether it held.
┌───────────────────────────┐
│ Topology::discover() │ numaperf-topo
│ nodes · cpus · distances │ (runtime, once)
│ device → node map │
└────────────┬──────────────┘
│ read by every layer
┌────────────────┼─────────────────┬──────────────┐
▼ ▼ ▼ ▼
MemPolicy ScopedPin NumaExecutor device buffers
Bind/Pref/ (RAII pin, per-node pools on NIC/NVMe-
Interleave/ restore on node-scoped local node
Local + drop) stealing numaperf-io
Prefault::Touch numaperf- numaperf-sched
numaperf-mem affinity numaperf-sharded
└────────────────┴─────────────────┴──────────────┘
│
▼
┌──────────────────────┐
│ locality ratios, │ numaperf-perf
│ cross-node traffic, │ (did it hold?)
│ health reports │
└──────────────────────┘ The layers
Topology model
The foundation. Discovers nodes, CPUs, distances, and device→node maps at runtime. Every other layer reads from it, so no component hard-codes a machine layout.
Memory placement
Turns a topology plus an explicit MemPolicy (Bind / Preferred / Interleave / Local) into concrete allocations, with Prefault::Touch to force placement at allocation time.
Thread pinning
RAII affinity. ScopedPin constrains where a thread runs and restores the prior affinity on drop, so placement decisions survive refactors and pool transitions.
Scheduling & data
NumaExecutor runs per-node worker pools with node-scoped stealing; NumaSharded<T> and ShardedCounter keep data per-node so work runs where its data already is.
Observability
Closes the loop. Locality ratios, health reports, and cross-node traffic counters turn "is it actually local?" into a measured number.
Why application-layer, not kernel heroics
On multi-socket servers, cross-node memory access latency is typically 2–3× local access (per the project README). The kernel’s default first-touch policy places a page on whichever node first writes it, which is rarely the node you want under load. numaperf’s position is that the fix is not a smarter opaque scheduler but explicit, observable placement at the application layer: you choose the policy, you pin the thread, and you measure the locality ratio.
Pinning vs scheduling
Two concerns hide under "scheduling". Pinning is a constraint on where work runs; scheduling is a
policy for which work runs next. General-purpose async runtimes implement scheduling well and ignore
pinning entirely. numaperf takes pinning back with ScopedPin, and offers a scheduler
(NumaExecutor) that respects placement — while composing with your existing runtime for the
control plane. The hybrid pattern: keep the general runtime for control, carve out numaperf workers for the
latency-critical hot path, hand off over a bounded channel.
Incremental adoption
Because it is a workspace, you can adopt one crate at a time. Start with numaperf-topo to see
the machine, add numaperf-mem and numaperf-affinity for one hot buffer, and reach
for numaperf-sched only when you are ready to own scheduling. The glossary
defines the terms above.