I’ve posted my brief experiments with sync.Pool in go. However, reading about it guides that it may be hard to use tool. And sometimes it can degrade for third-party reasons. It is easy to trap into old information regarding sync.Pool and gc interrogation. Since go 1.13 sync.Pool is not cleared completely on every gc. However, it is still affected by gc. Suppose the following code:
type Obj []int
func (o *Obj) Fill() {
for i := 0; i < cap(*o); i++ {
*o = append(*o, i)
}
}
func (o Obj) Count() int {
result := 0
for i := 0; i < len(o); i++ {
result += o[i]
}
return result
}
func NewObj() *Obj {
o := make(Obj, 0, 100*1024)
return &o
}
var objPool = &sync.Pool{
New: func() interface{} {
return NewObj()
},
}
var ResultTrap int
func WorkWithPool() {
o := objPool.Get().(*Obj)
o.Fill()
ResultTrap = o.Count()
released := (*o)[:0]
objPool.Put(&released)
}
And let’s use it this way:
func BenchmarkPool(b *testing.B) {
b.Run("OnlyGC", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
runtime.GC()
}
})
b.Run("NoGC", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
WorkWithPool()
}
})
b.Run("OneGC", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
WorkWithPool()
runtime.GC()
}
})
b.Run("TwoGC", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
WorkWithPool()
runtime.GC()
runtime.GC()
}
})
b.Run("ThreeGC", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
WorkWithPool()
runtime.GC()
runtime.GC()
runtime.GC()
}
})
b.Run("FourGC", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
WorkWithPool()
runtime.GC()
runtime.GC()
runtime.GC()
runtime.GC()
}
})
}
Here are the results:
BenchmarkPool
BenchmarkPool/OnlyGC
BenchmarkPool/OnlyGC-8 69956 149475 ns/op 13 B/op 0 allocs/op
BenchmarkPool/NoGC
BenchmarkPool/NoGC-8 50950 225802 ns/op 40 B/op 1 allocs/op
BenchmarkPool/OneGC
BenchmarkPool/OneGC-8 29601 405608 ns/op 495570 B/op 4 allocs/op
BenchmarkPool/TwoGC
BenchmarkPool/TwoGC-8 19710 612537 ns/op 820298 B/op 5 allocs/op
BenchmarkPool/ThreeGC
BenchmarkPool/ThreeGC-8 15132 806346 ns/op 820312 B/op 5 allocs/op
BenchmarkPool/FourGC
BenchmarkPool/FourGC-8 12048 1012356 ns/op 820327 B/op 5 allocs/op
Even one garbage collection hugely affects allocations of this test. Two garbage collections affect it even more, while any additionals have no effect. Suppose you have some near zero-allocating application that uses sync.Pools heavily in various places. Any change leading to memory allocation big enough for garbage collection happening may lead to cascading failure of protection from allocations and hiding the original cause of the allocation spike.