Skip to content

Instantly share code, notes, and snippets.

@lbe
Created November 28, 2025 05:06
Show Gist options
  • Select an option

  • Save lbe/583661f8f276734b221188e199d0c930 to your computer and use it in GitHub Desktop.

Select an option

Save lbe/583661f8f276734b221188e199d0c930 to your computer and use it in GitHub Desktop.
Go benchmark that compares several pooling strategies
// Pool Benchmarks (Standalone Gist)
//
// This single-file Go benchmark compares several pooling strategies:
// - Interface sync.Pool with pointer indirection (e.g., *[]byte, *bytes.Buffer)
// - Generic value slice pool (SlicePool[T])
// - Generic pointer pool (PtrPool[T])
// - Minimal ResettablePool (calls Reset() on Put)
//
// Usage:
//
// go test -bench . -benchmem -run '^$'
//
// Benchmark groups:
//
// go test -bench 'BufPool' -benchmem -run '^$'
// go test -bench 'BufferPool' -benchmem -run '^$'
// go test -bench 'Null(String|Int64)Pool_(GetPut|Parallel)$' -benchmem -run '^$'
// go test -bench 'MD5_(GetPut|Parallel)$' -benchmem -run '^$'
// go test -bench 'GalleryImage_(GetPut|Parallel)$' -benchmem -run '^$'
//
// Notes:
// - The file is standalone (standard library only).
// - Results vary by CPU/Go version; focus on relative comparisons.
// - Generic value slice pools allocate; pointer pools avoid allocations.
package poolbench
import (
"bytes"
"crypto/md5"
"database/sql"
"image"
"image/color"
"sync"
"testing"
)
// Dummy references to satisfy standalone linters scanning file in isolation.
var (
_ = bytes.MinRead
_ = md5.BlockSize
_ = sql.ErrNoRows
_ = image.ZP
_ = color.RGBAModel
_ = testing.AllocsPerRun
)
// --- Minimal resettable pool (standalone) ---
type Resettable interface{ Reset() }
type ResettablePool struct{ p sync.Pool }
func NewResettablePool(newFn func() interface{}) *ResettablePool {
return &ResettablePool{p: sync.Pool{New: func() any { return newFn() }}}
}
func (rp *ResettablePool) Get() interface{} { return rp.p.Get() }
func (rp *ResettablePool) Put(v interface{}) {
if r, ok := v.(Resettable); ok {
r.Reset()
}
rp.p.Put(v)
}
// --- Generic slice and pointer pools ---
type SlicePool[T any] struct{ pool sync.Pool }
func NewSlicePool[T any](newFn func() T) *SlicePool[T] {
return &SlicePool[T]{pool: sync.Pool{New: func() any { return newFn() }}}
}
func (p *SlicePool[T]) Get() T { return p.pool.Get().(T) }
func (p *SlicePool[T]) Put(v T) { p.pool.Put(v) }
type PtrPool[T any] struct{ pool sync.Pool }
func NewPtrPool[T any](newFn func() T) *PtrPool[T] {
return &PtrPool[T]{pool: sync.Pool{New: func() any { return newFn() }}}
}
func (p *PtrPool[T]) Get() T { return p.pool.Get().(T) }
func (p *PtrPool[T]) Put(v T) { p.pool.Put(v) }
// --- Buffer and byte slice pools ---
var (
interfaceBufPool = sync.Pool{New: func() any { b := make([]byte, 32*1024); return &b }}
genericBufPool = NewSlicePool(func() []byte { return make([]byte, 32*1024) })
)
func touchBuffer(b []byte) {
for i := 0; i < 16; i++ {
b[i] = byte(i)
}
_ = b[0]
}
func BenchmarkInterfaceBufPool_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
bufPtr := interfaceBufPool.Get().(*[]byte)
buf := *bufPtr
touchBuffer(buf)
interfaceBufPool.Put(bufPtr)
}
}
func BenchmarkGenericBufPool_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
buf := genericBufPool.Get()
touchBuffer(buf)
genericBufPool.Put(buf)
}
}
func BenchmarkInterfaceBufPool_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
bufPtr := interfaceBufPool.Get().(*[]byte)
buf := *bufPtr
touchBuffer(buf)
interfaceBufPool.Put(bufPtr)
}
})
}
func BenchmarkGenericBufPool_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
buf := genericBufPool.Get()
touchBuffer(buf)
genericBufPool.Put(buf)
}
})
}
func BenchmarkInterfaceBufPool_Zeroing(b *testing.B) {
for i := 0; i < b.N; i++ {
bufPtr := interfaceBufPool.Get().(*[]byte)
buf := *bufPtr
for j := range buf {
buf[j] = 0
}
interfaceBufPool.Put(bufPtr)
}
}
func BenchmarkGenericBufPool_Zeroing(b *testing.B) {
for i := 0; i < b.N; i++ {
buf := genericBufPool.Get()
for j := range buf {
buf[j] = 0
}
genericBufPool.Put(buf)
}
}
// --- *bytes.Buffer pointer pools ---
var (
interfaceBufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }}
genericPtrBufferPool = NewPtrPool(func() *bytes.Buffer { return &bytes.Buffer{} })
)
func touchBytesBuffer(buf *bytes.Buffer) {
buf.Reset()
buf.Write([]byte("abcdefg"))
_ = buf.Len()
}
func BenchmarkInterfaceBufferPool_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := interfaceBufferPool.Get().(*bytes.Buffer)
touchBytesBuffer(v)
interfaceBufferPool.Put(v)
}
}
func BenchmarkGenericPtrBufferPool_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := genericPtrBufferPool.Get()
touchBytesBuffer(v)
genericPtrBufferPool.Put(v)
}
}
func BenchmarkInterfaceBufferPool_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := interfaceBufferPool.Get().(*bytes.Buffer)
touchBytesBuffer(v)
interfaceBufferPool.Put(v)
}
})
}
func BenchmarkGenericPtrBufferPool_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := genericPtrBufferPool.Get()
touchBytesBuffer(v)
genericPtrBufferPool.Put(v)
}
})
}
// --- sql.NullString / sql.NullInt64 pointer pools ---
var (
interfaceNullStringPool = sync.Pool{New: func() any { return &sql.NullString{} }}
genericPtrNullStringPool = NewPtrPool(func() *sql.NullString { return &sql.NullString{} })
)
func touchNullString(ns *sql.NullString) { ns.String = "x"; ns.Valid = true }
func BenchmarkInterfaceNullStringPool_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := interfaceNullStringPool.Get().(*sql.NullString)
touchNullString(v)
interfaceNullStringPool.Put(v)
}
}
func BenchmarkGenericPtrNullStringPool_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := genericPtrNullStringPool.Get()
touchNullString(v)
genericPtrNullStringPool.Put(v)
}
}
func BenchmarkInterfaceNullStringPool_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := interfaceNullStringPool.Get().(*sql.NullString)
touchNullString(v)
interfaceNullStringPool.Put(v)
}
})
}
func BenchmarkGenericPtrNullStringPool_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := genericPtrNullStringPool.Get()
touchNullString(v)
genericPtrNullStringPool.Put(v)
}
})
}
var (
interfaceNullInt64Pool = sync.Pool{New: func() any { return &sql.NullInt64{} }}
genericPtrNullInt64Pool = NewPtrPool(func() *sql.NullInt64 { return &sql.NullInt64{} })
)
func touchNullInt64(ni *sql.NullInt64) { ni.Int64 = 1; ni.Valid = true }
func BenchmarkInterfaceNullInt64Pool_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := interfaceNullInt64Pool.Get().(*sql.NullInt64)
touchNullInt64(v)
interfaceNullInt64Pool.Put(v)
}
}
func BenchmarkGenericPtrNullInt64Pool_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := genericPtrNullInt64Pool.Get()
touchNullInt64(v)
genericPtrNullInt64Pool.Put(v)
}
}
func BenchmarkInterfaceNullInt64Pool_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := interfaceNullInt64Pool.Get().(*sql.NullInt64)
touchNullInt64(v)
interfaceNullInt64Pool.Put(v)
}
})
}
func BenchmarkGenericPtrNullInt64Pool_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := genericPtrNullInt64Pool.Get()
touchNullInt64(v)
genericPtrNullInt64Pool.Put(v)
}
})
}
// --- MD5 pools: resettable vs generic pointer of Resettable iface ---
var md5ResettablePool = NewResettablePool(func() interface{} { return md5.New() })
type hashIface = interface{ Reset() }
var md5GenericIfacePool = NewPtrPool(func() hashIface { return md5.New() })
func touchMD5(h hashIface) { h.Reset() }
func BenchmarkResettablePool_MD5_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := md5ResettablePool.Get().(hashIface)
touchMD5(v)
md5ResettablePool.Put(v)
}
}
func BenchmarkGenericPtrPool_MD5_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := md5GenericIfacePool.Get()
touchMD5(v)
md5GenericIfacePool.Put(v)
}
}
func BenchmarkResettablePool_MD5_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := md5ResettablePool.Get().(hashIface)
touchMD5(v)
md5ResettablePool.Put(v)
}
})
}
func BenchmarkGenericPtrPool_MD5_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := md5GenericIfacePool.Get()
touchMD5(v)
md5GenericIfacePool.Put(v)
}
})
}
// --- galleryImage type + pools ---
type galleryImage struct {
img *image.RGBA
}
func (g *galleryImage) Reset() {
// Cheap reset: clear pixel buffer
if g.img != nil && g.img.Pix != nil {
for i := range g.img.Pix {
g.img.Pix[i] = 0
}
}
// Also touch bounds to simulate light usage
_ = g.img.Bounds()
if g.img != nil {
g.img.Set(0, 0, color.RGBA{0, 0, 0, 0})
}
}
var galleryImagePool = NewResettablePool(func() interface{} {
return &galleryImage{img: image.NewRGBA(image.Rect(0, 0, 1920, 1080))}
})
var genericPtrGalleryImagePool = NewPtrPool(func() *galleryImage {
return &galleryImage{img: image.NewRGBA(image.Rect(0, 0, 1920, 1080))}
})
func touchGalleryImage(gi *galleryImage) { gi.Reset() }
func BenchmarkResettablePool_GalleryImage_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := galleryImagePool.Get().(*galleryImage)
touchGalleryImage(v)
galleryImagePool.Put(v)
}
}
func BenchmarkGenericPtrPool_GalleryImage_GetPut(b *testing.B) {
for i := 0; i < b.N; i++ {
v := genericPtrGalleryImagePool.Get()
touchGalleryImage(v)
genericPtrGalleryImagePool.Put(v)
}
}
func BenchmarkResettablePool_GalleryImage_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := galleryImagePool.Get().(*galleryImage)
touchGalleryImage(v)
galleryImagePool.Put(v)
}
})
}
func BenchmarkGenericPtrPool_GalleryImage_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
v := genericPtrGalleryImagePool.Get()
touchGalleryImage(v)
genericPtrGalleryImagePool.Put(v)
}
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment