func BenchmarkPool(b *testing.B) {
b.ReportAllocs()
p := sync.Pool{New: func() interface{} {
return make([]byte, 1024)
}}
for i := 0; i < b.N; i++ {
bts := p.Get().([]byte)
p.Put(bts)
}
}
这个基准测试在go1.19.5中给出了以下输出。
BenchmarkPool
BenchmarkPool-10 47578498 24.47 ns/op 24 B/op 1 allocs/op
使用*[]byte
时,情况会有所不同:
一个二个一个一个
似乎将interface{}
转换回切片会导致额外的堆分配。
Go为什么需要这个额外的分配,其下的设计考虑是什么?
2条答案
按热度按时间bwitn5fc1#
从您根据需要分配和丢弃标头和后备数组的意义上说,片是“动态的”。当您池化
[]byte
值时,总是复制片标头,因此实际上重用的是后备数组,并且每次都需要为标头分配额外的24个字节以将片返回到池中。换句话说,当你只传入一个拷贝时,你不能重用切片头。如果希望池存储和重用整个切片值,则需要使用
*[]byte
,因为需要一个指针指向要存储在池中的值,这样还可以向池返回不同大小的切片值(可能通过append进行放大),这对于您的用例可能是理想的,也可能不是理想的。如果您只想重用一个静态大小的缓冲区,那么您需要的是一个数组,或者更具体地说,是一个指向数组的指针,这样可以更清楚地了解池值的期望值。
您可以根据需要在该数组周围分配一个切片,并在将其返回到池时将其转换回数组。
kx1ctssn2#
不是
any
到[]byte
的转换导致分配,而是[]byte
到any
的转换导致分配。p.Put(bts)
隐式地将参数bts
转换为any
,然后将其传递给(*sync.Pool).Put
。GoGC 1.19中的接口被实现为一对指针,一个指向类型元数据,一个指向实际对象,在这种情况下,第二个指针逃逸到池,导致分配切片对象。这不仅适用于切片类型,也适用于任何其他非指针类型。对于指针,如
*[]byte
,编译器会执行一个优化,将其值直接放入iface
结构体,这样在转换为接口时会删除*[]byte
示例的分配。因此,通常建议将指针放入池中,而不是结构体本身。