了解 Sync.Pool


Go: 了解 Sync.Pool

在项目中的“垃圾回收”中经常会遇到这么一个问题。大量对象被重复分配,导致GC的工作量很大。使用sync.Pool,可以减少分配和GC工作量。

什么是 sync.Pool?

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

为什么要使用 sync.Pool?

我们希望尽可能减少GC的开销。 频繁分配和回收内存将给GC造成沉重负担。 sync.Pool可以缓存暂时不使用的对象,并在下次需要它们时直接使用它们(无需重新分配)。 这样可以潜在地减少GC工作量并提高性能。

如何使用 sync.Pool?

首先,您需要设一个 新的 function.。 当池中没有缓存的对象时,将使用此功能。 之后,您只需要使用Get和Put方法来检索和返回对象。 另外,Pool在首次使用后不得复制。

由于新函数类型为 func()interface {},因此给方法将返回 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"
}
Benchmark
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)
        }
    }
}

Benchmark result:

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

Everything* in life is a trade-off.

池也有其性能成本。使用sync.Pool比简单的初始化要慢得多。

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
        }
    })
}

Benchmark result:

BenchmarkPool
BenchmarkPool-8           283395016          4.40 ns/op
BenchmarkAllocation
BenchmarkAllocation-8    1000000000         0.344 ns/op
sync.Pool如何工作?

sync.Pool has two containers for objects: local pool (active) and victim cache (archived).

根据 sync/pool.go , package init function registers to the runtime as a method 去清理 pools. 此方法将由GC触发。

func init() {
   runtime_registerPoolCleanup(poolCleanup)
}

When the GC is triggered, objects inside the victim cache will be collected and then objects inside the local pool will be moved to the victim cache.

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
}

New objects are put in the local pool. Calling Put method will put the object into the local pool as well. Calling Get method will take an object from the victim cache in the first place and if the victim cache was empty the object will be taken from the local pool.

[r9F2VK.gif

sync.Pool localPool and victimCache

For your information, the Go 1.12 sync.Pool implementation uses a mutex based locking for thread-safe operations from multiple Goroutines. Go 1.13 introduces a doubly-linked list as a shared pool which removes the mutex lock and improves the shared access.

结论

When there is an expensive object you have to create it frequently, it can be very beneficial to use sync.Pool.

PS:

个人英语水平有限,大部分内容觉得原文跟容易理解,翻译之后就变得怪怪的,因此部分内容保留原文。不懂英语不是不想学的借口。


 上一篇
Kubernetes on CRI-O (CentOS) Kubernetes on CRI-O (CentOS)
Kubernetes on CRI-O (CentOS)Docker 现在在 Kubernetes 中已弃用 。kubelet 中的 Docker 支持现已弃用,并将在以后的版本中删除。Kubelet 使用一个名为 dockershim的模
2020-12-15
下一篇 
CPU占用过高排查 CPU占用过高排查
CPU占用过高排查一.简介最近一段时间 某台服务器上的一个应用总是隔一段时间就自己挂掉 用top看了看 从重新部署应用开始没有多长时间CPU占用上升得很快 排查步骤 1.使用top 定位到占用CPU高的进程PIDtop 2.通过ps aux
2020-12-08
  目录