Skip to content

goodhistogram: perf-eval of exact min/max tracking on the hot path - #12

Draft
angles-n-daemons wants to merge 3 commits into
mainfrom
bdillmann/minmax-perf-eval
Draft

goodhistogram: perf-eval of exact min/max tracking on the hot path#12
angles-n-daemons wants to merge 3 commits into
mainfrom
bdillmann/minmax-perf-eval

Conversation

@angles-n-daemons

@angles-n-daemons angles-n-daemons commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

What

Evaluates the cost of tracking the exact minimum and maximum value observed
by a goodhistogram, added to the lock-free Record hot path.

Today Record uses fetch-and-add for the bucket counters and sum. Exact
extremes can't use fetch-and-add — each needs a load-guarded compare-and-swap:

for {
    old := dst.Load()
    if v >= old { return }              // guard: skip CAS unless v is a new extreme
    if dst.CompareAndSwap(old, v) { return }
}

The guard load short-circuits the steady state, so the CAS only fires when an
extreme actually moves. This PR adds experimental RecordMinMax /
RecordMinMaxPadded variants plus an A/B benchmark, and writes up the results.
It is a measurement branch, not a merge candidate — no change to the default
Record.

How it was measured

Three variants A/B'd: baseline (today's Record), minmax (two atomic.Int64
inline next to sum), and padded (each extreme on its own cache line, to
isolate false sharing). Independent variable is input orderingsteady
(shuffled), ascending (new max every call), descending (new min every call) —
crossed with concurrency (1 / 50 / 100 goroutines), -count=8, benchstat, on
three machines:

  • amd64 server — GCE, 24 vCPU x86_64, Go 1.25.5 (low variance → most reliable)
  • arm64 server — GCE t2a-standard-8, 8 vCPU Ampere Altra (Neoverse N1), Go 1.25.5
  • arm64 laptop — Apple M3 Pro (noisy under contention)

Results — single-threaded (steady, sec/op vs. baseline)

machine baseline inline minmax padded
amd64 server (24 vCPU) 20.9n 23.0n (+10.3%) 22.7n (+8.9%)
arm64 server (Ampere, 8) 13.4n 15.1n (+12.9%) 15.1n (+12.7%)
arm64 laptop (M3 Pro) 2.75n 3.46n (+25.6%) 3.45n (+25.3%)

Single-thread variance is ±0–2% on both servers → all deltas significant
(p < 0.001). Ordering (steady/ascending/descending) changes nothing (within ~1%).

Results — high contention (representative, sec/op vs. baseline)

machine / case baseline inline minmax padded
amd64, g=50 steady 39.7n 43.8n (+10.4%) 44.2n (+11.3%)
amd64, g=100 descending 38.3n 42.8n (+11.8%) 44.7n (+16.9%)
arm64 srv, g=50 steady 101.8n 94.5n (−7.1%) 94.6n (−7.1%)
arm64 srv, g=100 steady 100.2n 96.7n (~, n.s.) 100.1n (~, n.s.)
M3, g=50 steady 43.4n 51.8n (+19.3%) 54.9n (+26.4%)
M3, g=100 descending 41.1n 37.0n (~, n.s.) 63.9n (+55.4%)

Only the x86 server gives a clean contention signal (~+10%); both arm machines
are too noisy (±5–40%) to distinguish min/max from baseline.

Findings

  1. ~10–13% single-threaded Record overhead across all three machines
    (+10% x86, +13% Ampere arm, +26% M3 — higher only because the M3 baseline is
    ~5–8× faster in absolute terms). ~0.7–2 ns/op, zero allocations.
  2. Under contention the cost vanishes into the noise except on x86 (clean
    ~+10%). The contended sum.Add already dominates; read-mostly guard loads add
    little. Treat as "no clear regression."
  3. Input ordering doesn't matter — the guard load makes the CAS nearly free
    even for monotonic input. (Caveat: each goroutine replays the same array, so
    the shared extreme settles fast; an unbounded global monotonic stream would
    contend harder, untested.)
  4. Padding isn't worth it — and the "padding hurts" effect is Apple-M3-specific,
    not arm64-general.
    Padding tanked the M3 under contention (up to +55%) but is
    a wash on the Ampere arm server and x86. Co-locating the read-mostly extremes
    with the already-hot sum line (inline) is at least as good everywhere.

Recommendation

Exact min/max is cheap enough to ship: ~10–13% single-threaded Record overhead
(~0.7–2 ns/op), no clear contention regression, zero allocations — using the
inline load-guarded CAS (skip padding). If that ever matters on an ultra-hot
path, derive approximate extremes from populated bucket edges at Snapshot time
for zero hot-path cost (trading exactness for bucket-width error).

Full write-up and raw data under reports/minmax_perf_eval.md.

angles-n-daemons and others added 3 commits August 17, 2026 11:15
Add experimental RecordMinMax / RecordMinMaxPadded variants that track the
exact minimum and maximum observed value via a load-guarded CAS loop, plus an
A/B benchmark across input ordering (steady/ascending/descending) and
concurrency (1/50/100 goroutines).

Includes arm64 (Apple M3 Pro) results and write-up under reports/.

Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
Run the same A/B suite on gceworker-briandillmann (24 vCPU x86_64, Go 1.25.5)
and add a cross-architecture comparison. x86 shows a clean, consistent ~10%
Record overhead for inline min/max (arm64 M3 numbers were noisy under
contention). Padding is neutral-to-worse on both arches.

Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
…f-eval

Third architecture: GCE t2a-standard-8 (8 vCPU Ampere Altra, Neoverse N1).
Single-thread shows a clean ~+13% Record overhead (consistent with x86's +10%);
contention is noisy like the M3. Notably, cache-line padding is a wash here —
the "padding hurts" penalty seen on the Apple M3 is M3-specific, not arm64.

Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant