What are the tradeoffs of sync.Pool? When does it help, and when does it hurt?
Question 391HardGo 1.22 to 1.25
sync.Pool is a per-P cache of reusable temporary objects that reduces allocation rate and GC pressure for hot, short-lived objects (buffers, encoders). Semantics you must know:
- Items may be dropped at any time; the pool is cleared across GCs (since Go 1.13 via a victim cache, so objects survive one GC cycle). Never use it as a cache or connection pool.
Getmay return a dirty object — always reset it.- Store pointers: putting a
[]bytedirectly boxes the slice header into an interface, allocating on everyPut(staticcheck SA6002). - Don't return huge objects: one 10 MB request buffer returned to the pool pins that memory for everyone (see issue golang/go#23199). Cap the size you put back.
var bufPool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
const maxPooled = 64 << 10
func render(w io.Writer, v any) error {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer func() {
if buf.Cap() <= maxPooled {
bufPool.Put(buf)
}
}()
if err := json.NewEncoder(buf).Encode(v); err != nil {
return err
}
_, err := w.Write(buf.Bytes())
return err
}
It hurts when objects are cheap to allocate (small structs — the escape analysis/stack is faster), when allocation isn't in the profile, or when the lifecycle is unclear and an object is used after Put (a use-after-free style data race). Interviewer wants: "I'd add a pool only after alloc_space profiling shows the allocation site is hot, and I'd benchmark allocs/op before and after."
More on Performance, Profiling & Testing
- Q389Explain escape analysis. Which of these allocate on the heap?
- Q390What is bounds check elimination (BCE), how do you see it, and how can you help the compiler?
- Q392Compare the performance of string concatenation approaches: +, fmt.Sprintf, strings.Builder, bytes.Buffer, strings.Join.
- Q393What does this program do?
- Q394How do GOGC and GOMEMLIMIT interact, and how would you tune the GC for a latency-sensitive service?
- Q395Why does preallocating slices and maps matter? What does this print?