Compare · updated 2026-05-01
numaperf vs. Glommio
Glommio gets you thread-per-core with CPU affinity and io_uring on Linux. numaperf gets you NUMA topology, memory placement policies, and locality observability. They overlap on pinning and pair well on everything else.
| Dimension | numaperf | Glommio | Note |
|---|---|---|---|
| Primary concern | Memory placement, thread pinning, per-node scheduling, locality observability | Thread-per-core async runtime with io_uring and CPU pinning | Glommio is a runtime; numaperf is a set of primitives |
| Async model | Not an async runtime | async/await with a single-threaded executor per shard | |
| Execution shape | Sync threads, optionally inside another runtime | Thread-per-core; no work stealing across shards by design | Both philosophies push toward strict locality |
| CPU pinning | RAII ScopedPin (scoped, restored on drop) | Built into the runtime; each executor is pinned to a CPU | |
| NUMA topology API | Topology::discover() with nodes, CPUs, distances | Limited topology surface; CPU set / placement focused | |
| Memory placement policies | Bind, Preferred, Interleave, Local via NumaRegion + MemPolicy | Relies on placement-by-pinning and the system allocator | |
| Per-node scheduling | NumaExecutor with per-node pools, node-scoped work stealing | Per-CPU shards; explicit message passing between them | |
| Per-node data structures | NumaSharded<T>, ShardedCounter | Per-shard state by construction (each shard is single-threaded) | |
| Device locality | Map NICs and NVMe to NUMA nodes | io_uring per shard; device locality is the user’s responsibility | |
| Locality observability | Locality ratios, cross-node traffic counters, health reports | Standard runtime metrics; no NUMA-specific locality counters | |
| Linux dependency | Linux x86_64 / aarch64 (full); macOS graceful degradation | Linux-only (io_uring); recent kernel required | |
| MSRV | 1.70 | See Glommio release notes | |
| License | MIT | Apache-2.0 |
Why this comparison is useful
Glommio and numaperf are closer cousins than the Tokio comparison. Both projects start from the observation that the default scheduling and placement story on Linux leaves performance on the table for latency-critical workloads. They diverge on how aggressive a stance to take, and on what layer of the stack they live at.
- Glommio is a complete runtime. It picks a model — thread-per-core async with io_uring — and asks you to write your service in that model.
- numaperf is a set of primitives. It does not pick a runtime model; it gives you the building blocks (topology, pinning, placement, scheduling, observability) and lets you compose them with whatever your service already uses.
If you are starting a brand new service and the thread-per-core model is acceptable, Glommio is a coherent answer. If you are adding NUMA awareness to an existing service, or your runtime story is mixed, or you want NUMA primitives without committing to a runtime, numaperf is the more incremental path.
Where Glommio wins
- Coherent runtime story. You don’t have to compose two layers. The executor, the IO layer, and the placement are all of a piece.
- io_uring everywhere. If you want io_uring as your IO primitive across the entire service, Glommio is built around it.
- Thread-per-core enforced. The model removes the entire class of bugs caused by tasks migrating between shards. There is no “the task got stolen” failure mode.
- Mature runtime ergonomics. Spawning, channels, timers, IO — all there, all designed to be used together.
Where numaperf wins
- NUMA primitives are explicit.
MemPolicy::BindandNumaRegiongive you a vocabulary for memory placement that “pin the executor to a CPU” does not. Pinning gets you on the right node by accident; explicit placement gets you there on purpose. - Topology as data.
Topology::discover()returns the nodes, CPUs, and inter-node distances as a queryable object. You can branch on the distance matrix at startup. - Observability. Locality ratios and cross-node traffic counters per region are first-class. With a thread-per-core model you can infer that locality is good if you wrote everything right; numaperf lets you measure it.
- Composable, not all-or-nothing. You can adopt numaperf in just the hot path of an otherwise Tokio-based service. Glommio asks you to adopt its runtime.
- Device locality APIs. Mapping a NIC or NVMe device to its NUMA node is a numaperf concern; Glommio leaves it to the user.
When the two compose
There is no reason you cannot use both. A Glommio service can call Topology::discover() at startup to decide which CPUs to assign to its shards, and use NumaRegion for large buffers (database pages, packet pools) that it wants to bind to a specific node. The two projects don’t fight; they answer different questions.
The honest assessment is that if you already run Glommio, you have most of the pinning story handled — but you probably still want numaperf’s memory placement and observability primitives. If you don’t, you’re trusting first-touch to do the right thing on every allocation, which is the fragile policy the numaperf README explicitly calls out.
What we did not benchmark
We have not run head-to-head benchmarks between numaperf-based services and Glommio-based services. We do not want to claim numbers we did not measure. The comparison here is structural: it’s about what each tool gives you at the API and runtime level, not about which is faster on a particular workload. For perf claims, measure on your service. The observability primitives in numaperf-perf are designed to make that easy.
See something inaccurate about Glommio? Tell us and we’ll fix it.