我有一个简单的基准来比较创建结构切片和指向该结构的指针切片的性能
package pointer
import (
"testing"
)
type smallStruct struct {
ID int
}
func newSmallStruct(id int) *smallStruct {
return &smallStruct{ID: id}
}
func BenchmarkSmallStructPointer(b *testing.B) {
for n := 0; n < b.N; n++ {
var slice = make([]*smallStruct, 0, 10000)
for i := 0; i < 10000; i++ {
t := newSmallStruct(n + i)
slice = append(slice, t)
}
}
}
func BenchmarkSmallStruct(b *testing.B) {
for n := 0; n < b.N; n++ {
var slice = make([]smallStruct, 0, 10000)
for i := 0; i < 10000; i++ {
t := newSmallStruct(n + i)
slice = append(slice, *t)
}
}
}
基准测试结果
go test -bench . -benchmem
goos: linux
goarch: amd64
pkg: test-project/pointer
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkSmallStructPointer-4 3121 328864 ns/op 161921 B/op 10001 allocs/op
BenchmarkSmallStruct-4 29218 48021 ns/op 81920 B/op 1 allocs/op
请解释一下,什么操作为指针切片产生了如此多的分配?好像是追加到切片上,但我不明白为什么?
当然!指针切片的大量分配主要是由于每次调用 BenchmarkSmallStructPointer 函数中的append 都会为每个元素创建一个新指针。在此基准测试中,您将创建一个指向smallStruct 的指针切片。
让我们来分解一下:
在 BenchmarkSmallStructPointer 中,您正在创建一个指针切片 (var slice = make([]*smallStruct, 0, 10000))。 在循环内部,每次调用 t := newSmallStruct(n + i) 都会创建一个新的 *smallStruct 实例。 当您执行 slice =append(slice, t) 时,您将指针 t 附加到切片,并且由于每个 t 都是一个新指针,因此会导致大量分配。 将此与 BenchmarkSmallStruct 进行对比,在 BenchmarkSmallStruct 中创建实际结构的切片 (var slice = make([]smallStruct, 0, 10000))。在这种情况下,每个附加只有一次分配,因为您附加的是结构本身,而不是指针。
如果您想减少指针切片的分配,您可以考虑将切片预先分配到其最终大小(假设您提前知道),以最大程度地减少追加操作期间重新分配的需要。