Day 23 — sync.Pool & Memory Efficiency
Learning Objectives
- Reduce garbage collection pressure in hot paths using
sync.Pool - Benchmark allocations with
-benchmem - Understand when NOT to use
sync.Pool
Key Concepts
// sync.Pool — recycles objects to reduce GC pressure
// Pool contents are garbage collected between GC cycles (not permanent!)
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer) // called when pool is empty
},
}
// Get a buffer, use it, return it
func processRequest(body io.Reader) string {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset() // CRITICAL: reset before use
defer bufPool.Put(buf) // return after use
io.Copy(buf, body)
return buf.String()
}
// When NOT to use Pool:
// - Objects with long lifetimes (cache entries, connections)
// - Objects that hold references to other objects (pool contents are dropped on GC)
// - Small objects (allocation cost < pool overhead)
Exercise: Buffer Pool Benchmark
// pool_bench_test.go
package pool_test
import (
"bytes"
"io"
"strings"
"sync"
"testing"
)
var bufPool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
// Without pool: allocates new buffer every call
func processWithAlloc(body string) string {
var buf bytes.Buffer
io.Copy(&buf, strings.NewReader(body))
return buf.String()
}
// With pool: reuses buffers across calls
func processWithPool(body string) string {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
io.Copy(buf, strings.NewReader(body))
return buf.String()
}
func BenchmarkWithAlloc(b *testing.B) {
body := strings.Repeat("x", 4096)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
processWithAlloc(body)
}
}
func BenchmarkWithPool(b *testing.B) {
body := strings.Repeat("x", 4096)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
processWithPool(body)
}
}
// Run: go test -bench=. -benchmem -count=3 ./...
// Expected:
// BenchmarkWithAlloc 500000 2340 ns/op 4096 B/op 1 allocs/op
// BenchmarkWithPool 800000 1480 ns/op 0 B/op 0 allocs/op
Real-World Context
Your WAF body scanner reads and buffers every request body. At 10k rps with 4KB average body: without pool = 40MB/s of allocations = constant GC. With pool = 0 allocations in steady state. This is why high-performance proxies (Envoy, nginx) use pre-allocated buffer rings.