Skip to content

05 · Benchmarks

Status: recorded evidence against Doc 01 §6 criterion 2 (“The contract benchmarks true: C1–C4 demonstrated with numbers … ns-scale p99, zero allocations, linear scaling with writer threads”). Scope: the production engine (repo-root package), not the D2 simulation harness — Doc 04 already covers that decision (Option A, chip-mask schedule M3). Hardware: Apple M4 Pro (12 logical CPUs), via go test -bench. Numbers will vary by machine; the shape of each result (flat/scaling/degrading) is what matters.


1. Single-threaded Observe (C2: ns-scale, allocation-free)

Section titled “1. Single-threaded Observe (C2: ns-scale, allocation-free)”

Steady-state cost with a large table and a small cycling keyset, so calls land on the Phase 1 refresh path after the first pass:

BenchmarkSketch_Observe_SingleThreaded-12 1000000 20.95 ns/op 0 B/op 0 allocs/op

~21 ns/op, zero allocations — comparable to a handful of memory accesses, meeting C2 directly.


2. Parallel throughput across GOMAXPROCS (C4: linear scaling)

Section titled “2. Parallel throughput across GOMAXPROCS (C4: linear scaling)”

Each worker goroutine observes a disjoint window of keys (low contention by design — the best-case scaling story), run at -cpu=1,2,4,8,12:

GOMAXPROCS ns/op allocs/op
1 39.46 0
2 38.47 0
4 30.80 0
8 31.75 0
12 32.68 0

Per-op latency stays flat (even improves slightly) as core count rises, which means aggregate throughput scales near-linearly with writer threads — zero allocations at every level.


3. Worst-case single-key contention (edge case: every goroutine, one word)

Section titled “3. Worst-case single-key contention (edge case: every goroutine, one word)”

All goroutines racing the exact same slot on every call — the pathological case bounded CAS retry (≤3, then drop-and-continue) exists for:

GOMAXPROCS ns/op allocs/op
1 10.46 0
2 18.06 0
4 36.35 0
8 78.09 0
12 144.5 0

Cost grows with contention, as expected, but stays bounded — no livelock, no unbounded blowup, and zero allocations even under maximal contention. The correctness side of this same scenario (bounded, non-catastrophic loss rate, not corruption) is validated separately by the concurrency stress test.


Superseded by Doc 06 (06-salt-contention-decision.md). The table below predates two benchmark-isolation fixes — disjoint per-worker bucket windows and per-worker stats — without which lockstep bucket CAS and shared stats adds contaminated both variants and understated the salt’s true cost. Doc 06 has the corrected measurements (the real gap is ~12× at 8 cores, anti-scaling) and closes the call left open here: the shared salt stays. The original run is preserved below for history.

The structure-wide eviction salt (Doc 02 §4.1’s Option A / schedule M3 chip-mask lottery) is a single cache-line-padded atomic.Uint64 incremented by every evicting goroutine, across every bucket — a known contention point even after padding, since padding only prevents false sharing with unrelated fields, not contention on the shared word itself. Compared here against a variant giving each goroutine its own independent salt (not a shippable alternative — the anti-replay fix this salt exists for needs a genuinely shared, structure-wide counter — purely an isolation measurement):

GOMAXPROCS Shared salt (ns/op) Per-goroutine salt (ns/op) Overhead
1 12.49 12.68 ~0%
2 45.07 25.10 ~80%
4 50.97 37.98 ~34%
8 60.39 44.89 ~35%
12 78.35 65.36 ~20%

The shared salt is a real, measurable contention cost under eviction-heavy load — cache-line padding alone does not eliminate it, confirming the concern it was written to flag. The overhead is moderate (20–35% at higher core counts, not a multiple), not catastrophic, but it is real. Whether to escalate (shard the salt, or drop the per-attempt increment on the concurrent path — keeping it sequenced only on the serial differential-test path) is an open call, not made here.


  • All benchmarks use testing.B.ReportAllocs(); every result above is 0 allocs/op.
  • Single-threaded and parallel-distinct-key benchmarks pre-generate their key pools outside the timed region (b.ResetTimer() after setup) so reported costs reflect Observe itself, not key formatting.
  • The parallel and contention benchmarks use b.RunParallel, which Go’s testing package fans out across GOMAXPROCS automatically; -cpu=1,2,4,8,12 reruns the whole binary once per value to produce the scaling tables above.