Skip to content

feat(sync): add Pool - #2

Open
einouqo wants to merge 1 commit into
go101:mainfrom
einouqo:feature/sync-pool
Open

feat(sync): add Pool#2
einouqo wants to merge 1 commit into
go101:mainfrom
einouqo:feature/sync-pool

Conversation

@einouqo

@einouqo einouqo commented Jul 21, 2026

Copy link
Copy Markdown

I've found myself writing boilerplate-like code with standard sync.Pool, something like the following:

type foo struct {
	bar []byte
}

func (f *foo) init() *foo {
	f.bar = make([]byte, 0, 1<<8)
	return f
}

type fooPool struct {
	inner sync.Pool
}

func (p *fooPool) init() *fooPool {
	p.inner.New = func() any { return new(foo).init() }
	return p
}

func (p *fooPool) Put(o *foo) {
	p.inner.Put(o)
}

func (p *fooPool) Get() *foo {
	return p.inner.Get().(*foo)
}

var pool = new(fooPool).init()

func buz() {
	f := pool.Get()
	defer pool.Put(f)
	f.bar = f.bar[:0]
}

Instead, I'd prefer to have something slightly less verbose like so:

type foo struct {
	bar []byte
}

func (f *foo) init() *foo {
	f.bar = make([]byte, 0, 1<<8)
	return f
}

var pool = Pool[*foo]{
	New: func() *foo { return new(foo).init() },
	Reset: func(f *foo) *foo {
		f.bar = f.bar[:0]
		return f
	},
}

func main() {
	f := pool.Get()
	defer pool.Put(f)
	// No need to additionally reset value after receiving it from the pool
}

With the benchmark it seems like the overhead is about ~3% on average for my machine

func BenchmarkPool(b *testing.B) {
	b.Run("std", func(b *testing.B) {
		b.ReportAllocs()
		p := sync.Pool{
			New: func() any { return new(int) },
		}

		b.RunParallel(func(pb *testing.PB) {
			for pb.Next() {
				v := p.Get().(*int)
				p.Put(v)
			}
		})
	})

	b.Run("nstd", func(b *testing.B) {
		b.ReportAllocs()
		p := Pool[*int]{
			New: func() *int { return new(int) },
		}

		b.RunParallel(func(pb *testing.PB) {
			for pb.Next() {
				v := p.Get()
				p.Put(v)
			}
		})
	})
}

func BenchmarkPoolWithReset(b *testing.B) {
	b.Run("std", func(b *testing.B) {
		b.ReportAllocs()
		p := sync.Pool{
			New: func() any { return new(int) },
		}

		b.RunParallel(func(pb *testing.PB) {
			for pb.Next() {
				v := p.Get().(*int)
				*v = 0
				p.Put(v)
			}
		})
	})

	b.Run("nstd", func(b *testing.B) {
		b.ReportAllocs()
		p := Pool[*int]{
			New: func() *int { return new(int) },
			Reset: func(v *int) *int {
				*v = 0
				return v
			},
		}

		b.RunParallel(func(pb *testing.PB) {
			for pb.Next() {
				v := p.Get()
				p.Put(v)
			}
		})
	})
}

If you find the tradeoff reasonable, please consider the PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant