A sharded, generic object pool for Go with configurable growth and cleanup. It outperforms sync.Pool when objects are held longer between Get and Put, at the cost of extra overhead when retention is very short.
go get github.com/AlexsanderHamir/GenPool- Define a type that embeds
pool.Fields[YourType]so it implementsPoolable. - Provide an allocator and a cleaner (called on each Put).
- Create the pool with
NewPoolorNewPoolWithConfigand call Get/Put.
package main
import (
"github.com/AlexsanderHamir/GenPool/pool"
)
type Object struct {
Name string
Data []byte
pool.Fields[Object]
}
func main() {
allocator := func() *Object { return &Object{} }
cleaner := func(obj *Object) { obj.Name = ""; obj.Data = obj.Data[:0] }
p, err := pool.NewPool(allocator, cleaner)
if err != nil {
panic(err)
}
defer p.Close()
obj := p.Get()
obj.Name = "example"
obj.Data = append(obj.Data, 1, 2, 3)
p.Put(obj)
}With custom cleanup and growth:
config := pool.Config[Object, *Object]{
Cleanup: pool.DefaultCleanupPolicy(pool.GcModerate),
Allocator: allocator,
Cleaner: cleaner,
Growth: pool.GrowthPolicy{
Enable: true,
MaxPoolSize: 1000,
},
}
p, err := pool.NewPoolWithConfig(config)- Sharding — The pool is split into multiple shards, each with its own lock-free list to reduce contention; the number of shards is set via
Config.NumShards(defaulting toruntime.GOMAXPROCS(0)at creation) and can be overridden with a positive value for testing or tuning. - Growth — Without a growth policy, the pool grows unbounded. With
GrowthPolicy.EnableandMaxPoolSize, Get returnsnilwhen the cap is reached. - Cleanup — Optional background goroutine that periodically evicts objects whose usage count is below
MinUsageCount. Disabled by default; enable viaCleanupPolicyorDefaultCleanupPolicy(level).
| Level | Interval | MinUsageCount | Use case |
|---|---|---|---|
GcDisable |
— | — | No automatic cleanup |
GcLow |
10m | 1 | High reuse, low churn |
GcModerate |
2m | 2 | Default balance |
GcAggressive |
30s | 3 | Memory-sensitive / bursty |
Cleanup: pool.DefaultCleanupPolicy(pool.GcModerate)Or build a policy manually:
Cleanup: pool.CleanupPolicy{
Enabled: true,
Interval: 2 * time.Minute,
MinUsageCount: 2,
}Limit pool size so Get returns nil when full:
Growth: pool.GrowthPolicy{
Enable: true,
MaxPoolSize: 5000,
}If Enable is false, the pool has no size limit and relies on cleanup (if enabled) for reclaiming memory.
Benchmarks (darwin/arm64, Apple M1) show GenPool ahead when objects are held longer; sync.Pool can win when retention is very short.
- Run benchmarks locally:
go test -bench=. ./test/(see test/pool_benchmark_test.go). - Published results and analysis: benchmark_results_transparency — raw outputs per scenario and conclusion.
Tip: Keep pool.Fields[Object] on its own cache line (e.g. add padding) to reduce false sharing under contention.
Disable automatic cleanup with GcDisable. You can then implement custom eviction by using the exported ShardedPool.Shards and each Shard's Head (and Single) to traverse or clear lists. The pool does not lock these; coordinate with Get/Put usage as needed.
- Go 1.24+ required.
- Run tests and benchmarks before changing behavior:
go test ./...andgo test -bench=. ./.... - Add tests for new behavior and update docs for user-facing changes.
- All CI checks must pass for PRs to be merged.
git clone https://github.com/AlexsanderHamir/GenPool.git
cd GenPool
go test -v ./...
golangci-lint run # optionalMIT. See LICENSE.