我最近在一個項目中遇到了垃圾回收問題。大量對象被重複分配,並致使 GC 的巨大工做量。使用 sync.Pool
,我可以減小分配和 GC 工做負載。緩存
Go 1.3 版本的亮點之一是同步池。它是 sync
包下的一個組件,用於建立自我管理的臨時檢索對象池。安全
咱們但願儘量減小 GC 開銷。頻繁的內存分配和回收會給 GC 帶來沉重的負擔。sync.Poll
能夠緩存暫時不使用的對象,並在下次須要時直接使用它們(無需從新分配)。這可能會減小 GC 工做負載並提升性能。ide
首先,您須要設置新函數。當池中沒有緩存對象時將使用此函數。以後,您只須要使用 Get
和 Put
方法來檢索和返回對象。另外,池在第一次使用後絕對不能複製。函數
因爲 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.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...
」
來都來了,點個「再看」再走叭~~