Reuse scratch subject buffer in processMsgResults to eliminate heap allocation on hot
perfloop/nats-server · ALLOCATION HOT LOOP
https://perfloop.ai/t/oss/case_75tfr1qge9
Verdict
VALIDATED BY A DIFFERENT CHANGE · settled 2026-10-03
What happened: The paired measurements met the required improvement.
Hypothesis
Using `go build -gcflags=-m`, we observed: `server/client.go:5170:6: moved to heap: _dsubj`. Go memory allocation profiling on `BenchmarkPublish/MsgSz=32b/Subs=1xAsync` confirms that `_dsubj [128]byte` is heap-allocated on every message delivery, contributing 6,497,049 object allocations (~31.98% of the total). Because connection message processing is single-threaded per connection, we can safely persist and reuse this scratch buffer on the `client.in` or `client` struct to achieve allocation-free execution on this hot path.
Change to test: Move the scratch subject byte array `_dsubj` to be a persistent field on the `readCache` (or `client`) struct so it can be reused across calls without escaping to the heap on every message delivery.
Where it lives
perfloop/nats-server · server/client.go
Evidence
BenchmarkPublish/MsgSz=32b/Subs=1xAsync · 10 sample pairs
| metric | baseline | candidate | paired median change | confidence range | required | result |
|---|---|---|---|---|---|---|
ns/op |
351.1 |
316.4 |
−9.2% (−32.35) |
−46.8 to −12.8 |
≤ 0 |
PASSED |
MB/s |
91.16 |
101.2 |
+10.2% (+9.34) |
+3.43 to +14.21 |
≥ 0 |
PASSED |
%error |
0 |
0 |
0 |
0 to 0 |
≤ 0 |
PASSED |
B/op |
269.5 |
146 |
−45.6% (−123) |
−124 to −122 |
< −13.48 |
PASSED |
allocs/op |
2 |
1 |
−50% (−1) |
−1 to −1 |
< −0.1 |
PASSED |
Checks: 6 of 6 passed. Verification: no defect found.
Timeline
2026-07-01· Case opened2026-10-02· PR opened2026-10-06· Case closed