Skip to content

perf: use zero-alloc xxh3.HashString on the local counter hot path#60

Merged
VojtechVitek merged 1 commit into
masterfrom
perf/xxh3-hashstring
Jun 26, 2026
Merged

perf: use zero-alloc xxh3.HashString on the local counter hot path#60
VojtechVitek merged 1 commit into
masterfrom
perf/xxh3-hashstring

Conversation

@VojtechVitek

Copy link
Copy Markdown
Contributor

While looking into #57 (raised by @lrstanley), the benchmarks surfaced that we were calling xxh3 sub-optimally: the local counter hashed keys via the streaming xxh3.New() API.

On realistic rate-limiter keys (IP, IP+method+path, IPv6):

approach ns/op allocs
xxh3.New() streaming (current) ~17–20 0
xxh3.HashString (this PR) ~1.5–3.6 0

xxh3.HashString(key) returns a bit-identical result to the streaming call for the same input (verified across empty, short, and long keys), so this is a ~10x speedup on the per-request hot path with no change in hash output.

Scope / what this intentionally does not change

The exported LimitCounterKey(key, window) is deliberately left on the streaming API. Making it zero-alloc would require concatenating the window string, which would change its output value and break key stability for external backends such as httprateredis. Only the unexported, in-memory limitCounterKey is changed (its values reset anyway, so there's no compatibility surface).

Credit to @lrstanley in #57 — the performance discussion there is what prompted this; the dependency itself stays.

  • go vet ./... clean, go test ./... passes

🤖 Prepared with the help of Claude (AI), reviewed before opening.

The local counter hashed keys via the streaming xxh3.New() API, which
benchmarks at ~17-20 ns/op. The one-shot xxh3.HashString produces a
bit-identical result for the same input and measures ~1.5-3.6 ns/op
(verified equivalent across empty, short and long keys), so this is a
~10x speedup on the per-request hot path with no change in hash output.

The exported LimitCounterKey is intentionally left on the streaming API:
making it zero-alloc would require concatenating the window string, which
would change its output value and break key stability for external
backends such as httprateredis.

This addresses the performance angle raised by @lrstanley in #57:
xxh3 was simply being called sub-optimally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Benchmark Results

goos: linux
goarch: amd64
pkg: github.com/go-chi/httprate
cpu: AMD EPYC 7763 64-Core Processor                
               │ master.txt  │            pr.txt             │
               │   sec/op    │   sec/op     vs base          │
LocalCounter-4   32.47m ± 1%   32.48m ± 3%  ~ (p=0.684 n=10)

               │  master.txt  │             pr.txt             │
               │     B/op     │     B/op      vs base          │
LocalCounter-4   2.860Mi ± 0%   2.860Mi ± 0%  ~ (p=0.971 n=10)

               │ master.txt  │            pr.txt             │
               │  allocs/op  │  allocs/op   vs base          │
LocalCounter-4   121.6k ± 0%   121.6k ± 0%  ~ (p=0.971 n=10)

@VojtechVitek VojtechVitek merged commit 81255de into master Jun 26, 2026
3 checks passed
@VojtechVitek VojtechVitek deleted the perf/xxh3-hashstring branch June 26, 2026 00:54
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