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

@zigo101 zigo101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I just noticed this PR.

@zigo101 zigo101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for submitting this PR. It is the first one on this project!

However, I would say sorry that I can't merge it.

My personal taste is that sync.Pool is not very useful.
It is best to write custom cache managers for your specific use cases.

Comment thread sync_test.go
pinner.Pin(&p)
p.Put(&runes[0])
p.Put(&runes[1])
if g := p.Get(); g == nil || *g != runes[0] {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Pinner maintain a FIFO queue? It looks the official docs don't guarantee this.

And, in theory, there might be several GC happened between any two lines of above.

Comment thread sync.go

type Pool[T any] struct {
pool sync.Pool
New func() T

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You used a non-interface version of New to deprecate sync.Pool.New. It looks to me a too larger change.

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.

2 participants