All Benchmarks

Go Benchmark

sync.Pool Buffer Reuse

Allocating a fresh buffer every time vs reusing one from sync.Pool.

sync-poolbufferallocationgcmemorybytes-buffer

Hot paths that need a temporary bytes.Buffer or byte slice face a choice: allocate a fresh one each time and let the garbage collector clean up, or recycle buffers through sync.Pool. This benchmark writes a 1 KiB payload into a fresh buffer vs a pooled one. The allocation columns are the story: the fresh buffer allocates its backing array every single iteration (feeding the GC), while the pooled buffer allocates once, and every reuse after that is allocation-free. This is exactly how fmt, net/http, and most encoders keep their per-request allocations near zero — at the price of Reset() discipline and objects that may be dropped by the GC between uses.

linux/amd64AMD Ryzen 9 9950X3D 16-Core Processorbenchmarks/sync-pool
Compare atCPUs

1 CPU

Fastest

Sync Pool

13.5 ns/op

Slowest

New Buffer

179.98 ns/op · 13x slower

32 CPUs

Fastest

Sync Pool

16.16 ns/op

Slowest

New Buffer

206.04 ns/op · 13x slower

Performance Comparison (lower is better)
CPU:
#1

Sync Pool #

Fastest

Fetches a *bytes.Buffer from a sync.Pool, calls Reset(), writes the payload, and returns it with Put. After the first iteration warms the pool, the buffer's backing array is reused and the loop runs with zero allocations. Note the obligations: always Reset() before use, never keep a reference after Put, and don't rely on the pool for correctness — the GC may empty it at any time.

CPU Scaling (lower is better)
// payloadSize is the number of bytes written into the buffer per iteration.
const payloadSize = 1024

func BenchmarkSyncPool_run(b *testing.B) {
	pool := sync.Pool{
		New: func() any { return new(bytes.Buffer) },
	}
	payload := bytes.Repeat([]byte("a"), payloadSize)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		buf := pool.Get().(*bytes.Buffer)
		buf.Reset()
		buf.Write(payload)
		sink = buf.Len()
		pool.Put(buf)
	}
}
1 CPU
13.3xfaster(1233%)thanNew Buffer
32 CPUs
12.8xfaster(1175%)thanNew Buffer
#2

New Buffer #

Slowest

Creates a fresh bytes.Buffer per iteration and writes the payload into it. Every iteration heap-allocates a new backing array (over 1 KiB here), all of which becomes garbage immediately — cheap to write, but it turns your hot path into a garbage-collector treadmill.

CPU Scaling (lower is better)
// payloadSize is the number of bytes written into the buffer per iteration.
const payloadSize = 1024

func BenchmarkNewBuffer_run(b *testing.B) {
	payload := bytes.Repeat([]byte("a"), payloadSize)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		buf := &bytes.Buffer{}
		buf.Write(payload)
		sink = buf.Len()
	}
}
1 CPU
13.3xslower(1233%)thanSync Pool
32 CPUs
12.8xslower(1175%)thanSync Pool

Contributors