Go Sync.Pool 背后的想法

我最近在一个项目中遇到了垃圾回收问题。大量对象被重复分配,并致使 GC 的巨大工做量。使用 sync.Pool,我可以减小分配和 GC 工做负载。缓存

什么是 sync.Pool?

Go 1.3 版本的亮点之一是同步池。它是 sync 包下的一个组件,用于建立自我管理的临时检索对象池。安全

为何要使用 sync.Pool?

咱们但愿尽量减小 GC 开销。频繁的内存分配和回收会给 GC 带来沉重的负担。sync.Poll 能够缓存暂时不使用的对象,并在下次须要时直接使用它们(无需从新分配)。这可能会减小 GC 工做负载并提升性能。ide

怎么使用 sync.Pool?

首先,您须要设置新函数。当池中没有缓存对象时将使用此函数。以后,您只须要使用 GetPut 方法来检索和返回对象。另外,池在第一次使用后绝对不能复制。函数

因为 New 函数类型是 func() interface{}Get 方法返回一个 interface{}。为了获得具体对象,你须要作一个类型断言。性能

`// A dummy struct`
`type Person struct {`
 `Name string`
`}`
`// Initializing pool`
`var personPool = sync.Pool{`
 `// New optionally specifies a function to generate`
 `// a value when Get would otherwise return nil.`
 `New: func() interface{} { return new(Person) },`
`}`
`// Main function`
`func main() {`
 `// Get hold of an instance`
 `newPerson := personPool.Get().(*Person)`
 `// Defer release function`
 `// After that the same instance is`
 `// reusable by another routine`
 `defer personPool.Put(newPerson)`
 `// Using the instance`
 `newPerson.Name = "Jack"`
`}`

基准测试

`type Person struct {`
 `Age int`
`}`
`var personPool = sync.Pool{`
 `New: func() interface{} { return new(Person) },`
`}`
`func BenchmarkWithoutPool(b *testing.B) {`
 `var p *Person`
 `b.ReportAllocs()`
 `b.ResetTimer()`
 `for i := 0; i < b.N; i++ {`
 `for j := 0; j < 10000; j++ {`
 `p = new(Person)`
 `p.Age = 23`
 `}`
 `}`
`}`
`func BenchmarkWithPool(b *testing.B) {`
 `var p *Person`
 `b.ReportAllocs()`
 `b.ResetTimer()`
 `for i := 0; i < b.N; i++ {`
 `for j := 0; j < 10000; j++ {`
 `p = personPool.Get().(*Person)`
 `p.Age = 23`
 `personPool.Put(p)`
 `}`
 `}`
`}`

测试结果:测试

`BenchmarkWithoutPool`
`BenchmarkWithoutPool-8   160698 ns/op   80001 B/op   10000 allocs/op`
`BenchmarkWithPool`
`BenchmarkWithPool-8      191163 ns/op       0 B/op       0 allocs/op`

权衡

生活中的一切都是一种权衡。池也有它的性能成本。使用 sync.Pool 比简单的初始化要慢得多。idea

`func BenchmarkPool(b *testing.B) {`
 `var p sync.Pool`
 `b.RunParallel(func(pb *testing.PB) {`
 `for pb.Next() {`
 `p.Put(1)`
 `p.Get()`
 `}`
 `})`
`}`
`func BenchmarkAllocation(b *testing.B) {`
 `b.RunParallel(func(pb *testing.PB) {`
 `for pb.Next() {`
 `i := 0`
 `i = i`
 `}`
 `})`
`}`

压测结果:spa

`BenchmarkPool`
`BenchmarkPool-8           283395016          4.40 ns/op`
`BenchmarkAllocation`
`BenchmarkAllocation-8    1000000000         0.344 ns/op`

sync.Pool 是如何工做的?

sync.Pool 有两个对象容器: 本地池 (活动) 和受害者缓存 (存档)。线程

根据 sync/pool.go ,包 init 函数做为清理池的方法注册到运行时。此方法将由 GC 触发。code

`func init() {`
 `runtime_registerPoolCleanup(poolCleanup)`
`}`

当 GC 被触发时,受害者缓存中的对象将被收集,而后本地池中的对象将被移动到受害者缓存中。

`func poolCleanup() {`
 `// Drop victim caches from all pools.`
 `for _, p := range oldPools {`
 `p.victim = nil`
 `p.victimSize = 0`
 `}`
 `// Move primary cache to victim cache.`
 `for _, p := range allPools {`
 `p.victim = p.local`
 `p.victimSize = p.localSize`
 `p.local = nil`
 `p.localSize = 0`
 `}`
 `oldPools, allPools = allPools, nil`
`}`

新对象被放入本地池中。调用 Put 方法也会将对象放入本地池中。调用 Get 方法将首先从受害者缓存中获取对象,若是受害者缓存为空,则对象将从本地池中获取。

图片

供你参考,Go 1.12 sync.pool 实现使用基于 mutex 的锁,用于来自多个 Goroutines 的线程安全操做。Go 1.13 引入了一个双链表做为共享池,它删除了 mutex 并改善了共享访问。

结论

当有一个昂贵的对象须要频繁建立时,使用 sync.Pool 是很是有益的。

译自:https://medium.com/swlh/go-th...

图片

来都来了,点个“再看”再走叭~~             

图片

相关文章
相关标签/搜索