Reduce Go Allocations with sync.Pool
Reuse hot short-lived buffers through sync.Pool to cut garbage collection pressure on high-throughput paths.
Task
Introduce a sync.Pool for a frequently allocated buffer to reduce allocation churn.
When to use this
A high-throughput handler allocates the same buffer per request, and GC pressure limits throughput.
Prerequisites
- Go 1.21 or later
- A benchmark for the hot path
- Access to allocation profiling
Steps
1. Reuse buffers via a sync.Pool and reset on get.
var bufPool = sync.Pool{New: func() any { return make([]byte, 0, 32*1024) }}
func handle(data []byte) {
buf := bufPool.Get().([]byte)[:0]
defer bufPool.Put(buf)
buf = append(buf, data...)
}Expected output: Buffers are reused across requests instead of allocated per call.
2. Benchmark allocations before and after.
go test -bench=Handle -benchmem ./...Expected output: Allocs per op drop, and GC pause time under load falls.
Troubleshooting
Problem
Data leaks between uses
Solution
Reset the slice length to zero on get and never rely on pooled contents.
Problem
Pool grows unbounded
Solution
Cap the buffer size and avoid pooling very large slices that belong on the heap.
Limitations
- Pools do not help allocation-light paths.
- Retained memory can increase RSS.
- Correctness depends on resetting state.
What this recipe does
Introduce a sync.Pool for a frequently allocated buffer to reduce allocation churn.
A high-throughput handler allocates the same buffer per request, and GC pressure limits throughput.
Prerequisites
- Go 1.21 or later
- A benchmark for the hot path
- Access to allocation profiling
Procedure
- Reuse buffers via a sync.Pool and reset on get.
var bufPool = sync.Pool{New: func() any { return make([]byte, 0, 32*1024) }}
func handle(data []byte) {
buf := bufPool.Get().([]byte)[:0]
defer bufPool.Put(buf)
buf = append(buf, data...)
}
Expected result: Buffers are reused across requests instead of allocated per call.
- Benchmark allocations before and after.
go test -bench=Handle -benchmem ./...
Expected result: Allocs per op drop, and GC pause time under load falls.
How to confirm it worked
Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows benchmarks show fewer allocations per operation and GC pause time drops without behaviour changes.
Troubleshooting
Data leaks between uses — Reset the slice length to zero on get and never rely on pooled contents.
Pool grows unbounded — Cap the buffer size and avoid pooling very large slices that belong on the heap.
Limitations
- Pools do not help allocation-light paths.
- Retained memory can increase RSS.
- Correctness depends on resetting state.