Benchmarks¶
Published, reproducible microbenchmarks for the hot-path primitives in this
library. Every number below comes from a real go test -bench run on the
machine described in Environment — nothing here is estimated
or hand-edited. Re-run them yourself with the commands in
Reproducing and you should land in the same order of magnitude
(absolute ns/op varies with CPU, thermal state, and Go version).
These are single-library microbenchmarks (this library measured against itself across algorithms), not a head-to-head against other libraries. A cross-library comparison harness is tracked as future work in ENHANCEMENTS.md §8.4. For a feature-level positioning against alternatives, see the comparison table in the README.
Environment¶
| Field | Value |
|---|---|
| Go version | go1.26.4 |
| GOOS / GOARCH | darwin / arm64 |
| CPU | Apple M3 Pro (11 cores) |
-count |
3 |
| Benchmark flags | -bench=. -benchmem -run='^$' |
GOMAXPROCS was left at the default (number of logical CPUs), which is why the
benchmark names carry the -11 suffix. These are laptop numbers on a shared,
non-quiesced machine — treat the ratios between algorithms as meaningful and the
absolute values as indicative rather than authoritative. CI runs a bounded
subset (token bucket, GCRA, circuit breaker) on GitHub-hosted runners as a
regression gate; those numbers will differ because the runner hardware differs.
Results¶
Values are the median of 3 runs (-count=3), taken directly from the raw
output captured in the runs below. Allow on the local limiters is the common
single-decision hot path.
Rate limiters — single-decision hot path¶
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
TokenBucket_Allow_SingleKey |
187.7 | 0 | 0 |
TokenBucket_Allow_100Keys |
184.4 | 0 | 0 |
GCRA_Allow_SingleKey |
189.0 | 0 | 0 |
GCRA_Allow_100Keys |
194.2 | 0 | 0 |
FixedWindow_Allow |
187.3 | 0 | 0 |
SlidingWindowCounter_Allow |
187.4 | 0 | 0 |
SlidingWindowCounter_Allow_100Keys |
189.6 | 0 | 0 |
SlidingWindowLog_Allow |
222.3 | 24 | 0 |
SlidingWindowLog_Allow_100Keys |
233.2 | 2 | 0 |
LeakyBucket_Allow_SingleKey |
4804 | 268 | 2 |
LeakyBucket_Allow_100Keys |
65655 | 7722 | 2 |
Notes:
- Token bucket, GCRA, fixed window, and sliding-window counter are all 0-alloc, ~185–195 ns/op for a single admission decision on this CPU.
- Sliding-window log carries a small per-op byte cost because it maintains a
timestamp log per key;
allocs/opstill amortizes to 0 in these runs. - Leaky bucket is intentionally different: it is a queue-and-drain shaper, so
Allowmay block on the drain interval. Its ns/op reflects that queuing behaviour, not per-decision overhead — do not compare it directly against the non-blocking limiters above. The100Keysfigure is dominated by many concurrent short sleeps and is the noisiest number in the set.
Rate limiters — parallel and multi-token paths¶
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
TokenBucket_Allow_Parallel |
288.2 | 0 | 0 |
TokenBucket_AllowN_5 |
294.4 | 336 | 2 |
TokenBucket_Peek |
274.0 | 360 | 5 |
GCRA_Allow_Parallel |
285.5 | 0 | 0 |
FixedWindow_Allow_Parallel |
266.1 | 0 | 0 |
SlidingWindowCounter_Allow_Parallel |
286.1 | 0 | 0 |
SlidingWindowCounter_Peek |
244.9 | 351 | 3 |
SlidingWindowLog_Allow_Parallel |
356.8 | 6 | 0 |
LeakyBucket_Allow_Parallel |
4049 | 264 | 2 |
Notes:
- The
Parallelvariants useb.RunParallel, so they include lock contention on the per-limiter key map. The single globalsync.RWMutexper limiter is the contention point; sharding it is tracked in ENHANCEMENTS.md §3.1. AllowNandPeekallocate because they populateResult.Metadata(amap[string]any). Making metadata lazy is tracked in ENHANCEMENTS.md §3.2.
Circuit breaker¶
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
CB_Execute_Closed |
107.7 | 0 | 0 |
CB_Execute_Open |
146.1 | 48 | 1 |
CB_Execute_Closed_Parallel |
222.3 | 0 | 0 |
Notes:
- The closed path (the common case: dependency healthy) is 0-alloc, ~108 ns/op. It is a CAS-based counter update.
- The open path allocates one object: the
*CircuitErrorreturned to the caller when the breaker short-circuits.
Coverage and gaps¶
Benchmarks exist today for the packages above. The following have no benchmarks yet and are noted as future work rather than represented with fabricated numbers:
bulkhead— no benchmark file present.ratelimit/adaptive,ratelimit/composite,ratelimit/tiered— no benchmarks.pipeline,retry,timeout,fallback— no benchmarks.ratelimit/middleware(HTTP/gRPC) — no benchmarks.- Distributed / Redis paths — no distributed benchmarks. Adding these
(against
miniredisfor CI and real Redis for nightly) is tracked in ENHANCEMENTS.md §3.3.
Adding these is a good first contribution — see docs/good-first-issues.md.
Methodology¶
- Each benchmark is run with
-count=3and the reported value is the median of the three runs (chosen to reduce the influence of a single noisy sample). -run='^$'disables unit tests so only benchmarks execute.-benchmemreportsB/opandallocs/opalongsidens/op.- Numbers were not post-processed beyond selecting the median; the raw captured output is shown below for auditability.
- For statistically rigorous A/B comparisons (e.g. a PR vs
main), usebenchstatviamake bench-compare OLD=main NEW=HEADormake bench-stat BASE=old.txt HEAD=new.txt, which reports deltas with confidence intervals instead of raw medians.
Reproducing¶
The exact command used to produce the rate-limiter and circuit-breaker tables:
go test -bench=. -benchmem -run='^$' -count=3 \
./ratelimit/tokenbucket/ \
./ratelimit/gcra/ \
./ratelimit/fixedwindow/ \
./ratelimit/slidingwindow/ \
./ratelimit/leakybucket/ \
./circuitbreaker/
Convenience Makefile targets:
make bench # every benchmark in the repo, -benchmem -count=5, tee'd to bench-<version>.txt
make bench-ci # the bounded CI regression set (token bucket, GCRA, circuit breaker)
Environment facts were captured with:
go version
go env GOOS GOARCH
sysctl -n machdep.cpu.brand_string # macOS; on CI, note the GitHub runner class instead
Raw output (excerpt)¶
Captured verbatim from the run described above (trimmed to representative lines;
-count=3 means each benchmark appears three times in the full log):
goos: darwin
goarch: arm64
pkg: github.com/sanskarpan/Rate-Limiter-Circuit-Breaker/ratelimit/tokenbucket
cpu: Apple M3 Pro
BenchmarkTokenBucket_Allow_SingleKey-11 6746175 182.0 ns/op 0 B/op 0 allocs/op
BenchmarkTokenBucket_AllowN_5-11 4008825 294.4 ns/op 336 B/op 2 allocs/op
BenchmarkTokenBucket_Allow_Parallel-11 4425452 275.6 ns/op 0 B/op 0 allocs/op
pkg: github.com/sanskarpan/Rate-Limiter-Circuit-Breaker/ratelimit/gcra
BenchmarkGCRA_Allow_SingleKey-11 6470257 189.0 ns/op 0 B/op 0 allocs/op
BenchmarkGCRA_Allow_Parallel-11 4326399 285.5 ns/op 0 B/op 0 allocs/op
pkg: github.com/sanskarpan/Rate-Limiter-Circuit-Breaker/ratelimit/fixedwindow
BenchmarkFixedWindow_Allow-11 6346656 187.3 ns/op 0 B/op 0 allocs/op
pkg: github.com/sanskarpan/Rate-Limiter-Circuit-Breaker/ratelimit/slidingwindow
BenchmarkSlidingWindowLog_Allow-11 5132568 220.1 ns/op 24 B/op 0 allocs/op
BenchmarkSlidingWindowCounter_Allow-11 6505830 187.4 ns/op 0 B/op 0 allocs/op
pkg: github.com/sanskarpan/Rate-Limiter-Circuit-Breaker/ratelimit/leakybucket
BenchmarkLeakyBucket_Allow_SingleKey-11 256716 4614 ns/op 269 B/op 2 allocs/op
pkg: github.com/sanskarpan/Rate-Limiter-Circuit-Breaker/circuitbreaker
BenchmarkCB_Execute_Closed-11 11452839 107.7 ns/op 0 B/op 0 allocs/op
BenchmarkCB_Execute_Open-11 7496500 146.1 ns/op 48 B/op 1 allocs/op