Skip to content
Cross-Platform Benchmarks

Cross-Platform Benchmarks

Cross-platform benchmarks

Per-primitive benchmark results across five CPU microarchitectures and four operating systems. Each cell is the Criterion median of the primitive’s own (MMF-backed) operation; baselines are shown where the contrast is the point (B-tree vs Mutex<BTreeMap>, blocked vs standard Bloom, shared cell vs RwLock).

ColumnCPUOSNotes
Zen+ / WinRyzen 7 2700 (Zen+)Windows 11the canonical published hardware
Zen2 / LinuxEPYC 7552 (Zen2)LinuxAVX2, no AVX-512
Zen3 / LinuxZen3Linux
Zen3 / FreeBSDZen3FreeBSD 15same silicon class as Zen3/Linux, different kernel
Ivy / macOSCore i5-3210M (Ivy Bridge)macOS 10.152012 2-core Intel outlier, not on the Zen ladder

Criterion --sample-size=12 --warm-up-time=1 --measurement-time=2, single-threaded unless the op name says otherwise. Cells marked † are a later re-capture on the Zen+ host; everything else is one sweep. All primitives run with the per-op observation sidecar gated by the process-global armed flag, so a raw handle pays a hot-path load and a predicted branch and nothing else - the numbers below are with observation present but unarmed, the production shape.

The four Zen columns read left to right as a clean microarchitecture ladder: Zen+ is roughly 2x the Zen3 across the board; Zen2 sits between. Zen3 on Linux and Zen3 on FreeBSD land within a few percent of each other (same silicon class), with each OS marginally ahead on different ops - the allocator and syscall paths differ even when the CPU does not. The fifth column is the outlier: a 2012 Intel Core i5 (Ivy Bridge) on macOS, a 13-year-old 2-core part that lands well behind the Zen chips and flips a handful of cells where its memory subsystem or low core count dominates rather than its clock.


Atomics, cells, and sub-nanosecond reads

The cheapest ops, where the push_op observation gate matters most: an unarmed observation is a single hot-global load, so these stay at the hardware floor.

OpZen+ / WinZen2 / LinuxZen3 / LinuxZen3 / FreeBSDIvy / macOS
SharedAtomicU64::load1.56 ns1.30 ns785 ps846 ps9.32 ns
SharedAtomicU64::fetch_add9.68 ns6.14 ns1.72 ns1.82 ns39.8 ns
SharedAtomicU64::compare_exchange9.80 ns6.20 ns2.17 ns2.23 ns35.4 ns
SharedBitVec::get2.62 ns2.14 ns1.35 ns1.38 ns6.55 ns
SharedBitVec::set7.73 ns6.66 ns2.56 ns2.56 ns34.5 ns
SharedBitVec::count_ones (1024 bits)40.2 ns33.9 ns19.3 ns19.7 ns106 ns
SharedCell::get3.75 ns2.97 ns2.23 ns2.13 ns10.4 ns
SharedCell::set13.7 ns12.2 ns3.17 ns3.46 ns42.8 ns
SharedRegion::get2.91 ns1.77 ns1.18 ns1.17 ns5.59 ns
SharedHashMap::len1.91 ns1.01 ns595 ps623 ps3.74 ns
SharedHistogram::count4.60 ns1.56 ns1.17 ns1.18 ns7.83 ns

SharedCell::get vs an in-process RwLock<struct> (Zen+ 3.75 vs 17.5 ns; Zen3 2.2 vs 4.3 ns; the 2012 Mac 10.4 vs 62.1 ns) is the lock-free-read win that holds on every platform.


Ordered and keyed maps

OpZen+ / WinZen2 / LinuxZen3 / LinuxZen3 / FreeBSDIvy / macOS
SharedHashMap::get14.7 ns12.1 ns7.82 ns8.13 ns34.1 ns
SharedHashMap::insert47.6 ns30.6 ns15.0 ns17.3 ns120 ns
SharedBTreeMap::get_hit (100 keys)25.2 ns †18.1 ns10.3 ns10.7 ns74.8 ns
  vs Mutex<BTreeMap>26.0 ns †20.8 ns11.9 ns11.7 ns90.9 ns
SharedBTreeMap::get_miss (100 keys)22.6 ns †19.3 ns11.9 ns10.4 ns73.3 ns
SharedBTreeMap::get_hit (100k keys)173 ns †183 ns108 ns106 ns438 ns
  vs Mutex<BTreeMap>109 ns †104 ns70.9 ns67.9 ns328 ns
SharedBTreeMap::iter_ascending (100)254 ns †171 ns112 ns107 ns1118 ns
SharedLRUCache::get31.7 ns22.5 ns9.90 ns9.70 ns109 ns
SharedLRUCache::put268 ns192 ns139 ns151 ns634 ns
SharedLRUCache::put_evict176 ns106 ns52.0 ns51.7 ns307 ns

† Zen+ / Win cells re-measured in a later run; see the note at the bottom of this page.

The B-tree, the substrate’s ordered-map primitive, leads Mutex<BTreeMap> at 100 keys on four of the five platforms - the mutex lock/unlock dominates at small N - and trails it at 100k keys, where the in-process map’s pointer-direct nodes win over the mmf’s seqlock + position-independent addressing. Zen+ / Windows is the exception at small N: on re-measurement the two are level on hits (25.2 vs 26.0), though the miss case still favours the B-tree. Read the small-N result as “a cross-process ordered map costs nothing here”, not as a win.


Probabilistic sketches

OpZen+ / WinZen2 / LinuxZen3 / LinuxZen3 / FreeBSDIvy / macOS
SharedBloomFilter::insert54.6 ns47.1 ns24.7 ns26.9 ns214 ns
SharedBloomFilter::contains (hit)42.2 ns33.2 ns20.4 ns22.3 ns125 ns
SharedBlockedBloomFilter::insert62.7 ns †45.8 ns20.9 ns20.4 ns211 ns
SharedBlockedBloomFilter::contains (16M, >L3)95.1 ns †133 ns49.7 ns48.8 ns271 ns
  vs standard Bloom (16M, >L3)161 ns †264 ns106 ns103 ns509 ns
SharedCountMinSketch::insert64.6 ns44.2 ns19.5 ns19.5 ns187 ns
SharedCountMinSketch::estimate25.3 ns14.3 ns8.27 ns8.25 ns84.5 ns
SharedHyperLogLog::insert20.0 ns16.2 ns9.38 ns9.71 ns66.1 ns
SharedHistogram::percentile_p99213 ns32.5 ns21.2 ns19.3 ns347 ns
SharedReservoirSampler::record (under cap)27.2 ns16.5 ns8.56 ns8.92 ns90.7 ns

The blocked Bloom’s single-cache-line contains is 1.7-2.1x the standard filter’s scattered-probe contains on every platform once the filter (16M items, ~19 MB) exceeds L3, where the standard filter’s n_hashes probes each miss to RAM. Below L3 the two tie (the out-of-order core overlaps the standard filter’s independent probes).


Coordination, clocks, and topology

OpZen+ / WinZen2 / LinuxZen3 / LinuxZen3 / FreeBSDIvy / macOS
SharedRateLimiter::try_acquire12.7 ns8.44 ns3.73 ns3.94 ns32.4 ns
SharedRwLock::try_read18.3 ns11.4 ns4.84 ns4.96 ns73.1 ns
SharedRwLock::try_write53.1 ns11.4 ns3.85 ns3.88 ns47.4 ns
FenceClock::tick12.7 ns11.6 ns9.18 ns8.28 ns21.2 ns
FenceClock::get_local15.0 ns8.22 ns8.32 ns8.27 ns21.0 ns
FenceClock::read_fence12.8 ns7.87 ns6.28 ns6.28 ns19.3 ns
TopologyMap::read_recommendation2.57 ns1.15 ns667 ps674 ps6.62 ns
TopologyMap::fan_out24.2 ns12.4 ns7.03 ns7.61 ns56.3 ns
TopologyMap::record_send21.2 ns11.7 ns5.43 ns5.22 ns37.3 ns

Sequences, regions, and graphs

OpZen+ / WinZen2 / LinuxZen3 / LinuxZen3 / FreeBSDIvy / macOS
SharedBroadcastRing::push66.6 ns39.3 ns19.6 ns19.3 ns164 ns
SharedBroadcastRing::recv59.2 ns37.4 ns17.5 ns17.4 ns186 ns
SharedBroadcastRing::lag2.17 ns1.13 ns717 ps735 ps5.32 ns
SharedLinkedList::push_back30.7 ns25.2 ns12.1 ns16.8 ns141 ns
SharedLinkedList::iter (100)378 ns259 ns167 ns171 ns1217 ns
SharedRegion::allocate75.4 ns39.4 ns29.1 ns30.5 ns137 ns
SharedRegion::alloc_free_cycle17.9 ns12.4 ns6.49 ns6.11 ns63.9 ns
SharedGraph::add_node162 µs15.8 µs13.7 µs14.7 µs715 µs
SharedGraph::add_edge149 µs18.0 µs13.8 µs14.3 µs1.15 ms
SharedGraph::neighbors (50)237 ns143 ns106 ns103 ns1172 ns

The SharedGraph::add_node / add_edge figures are cold-MMF measurements: the bench creates a fresh graph per iteration, so the timed add includes the first-write page-fault on a freshly mapped file. Windows mmap commit makes that page-fault roughly 10x the Linux cost, which is the microsecond-scale Zen+ column; the 2012 Mac’s mmap first-touch is slower still (715 µs add_node). Steady-state allocation into a warm region is the SharedRegion::allocate row (75 ns on Zen+).


Method

On the † cells. The shared_btree_map and shared_blocked_bloom_filter benches were re-run on the Zen+ / Windows host under the same flags, and those cells carry the later capture. They moved enough to change one conclusion, which is why they were replaced rather than left: SharedBTreeMap::get_hit at 100 keys had read as a 1.26x win over Mutex<BTreeMap> and re-measures as a tie. The other four columns are unchanged from the original sweep - they are different hosts and were not re-run - so the Zen+ column is a later capture than the row it sits in. That matters for reading a single row across platforms; it does not affect any within-column comparison, which is where every claim on this page actually lives.

Timings on the same host move 20-40% between runs. Ratios within a column are the durable part; the digits are one capture.

Each Zen platform ran the full Criterion suite - 68 bench targets today, shared_btree_map and shared_blocked_bloom_filter among them - under the same flags. The 2012 Mac ran the same suite minus the 8-thread capacity-adaptive-ring stress bench, which is pathological on a 2-core CPU. MMF files were backed by disk on every host (the benches that mmap large files need a backing store with free space, not a small tmpfs). The per-primitive reference pages carry the full op tables, contender rationale, and trade-off discussion for the canonical (Zen+) hardware; this page is the cross-platform summary.