我经常使用结构切片。以下是此类结构的示例:
type MyStruct struct {
val1, val2, val3 int
text1, text2, text3 string
list []SomeType
}
所以我定义我的切片如下:
[]MyStruct
假设我有大约一百万个元素,并且我正在大量使用切片:
我经常添加新元素。 (元素总数未知。)
我时不时地整理一下。
我还删除了元素(尽管不如添加新元素那么多)。
我经常阅读元素并将它们传递(作为函数参数)。
元素本身的内容不会改变。
我的理解是,这会导致实际结构的大量改组。另一种方法是创建一个指向结构的指针切片:
[]*MyStruct
现在结构保持在原来的位置,我们只处理我认为占用空间较小的指针,因此会使我的操作更快。但是现在我要给垃圾收集器更多的工作。
您能否提供有关何时直接使用结构与何时使用指向结构的指针的一般准则?
我应该担心留给 GC 的工作量吗?
复制结构与复制指针的性能开销可以忽略不计吗?
也许一百万个元素并不多。当切片变得更大(但仍然适合 RAM,当然)时,所有这些会如何变化?
[]T
and []*T
——大多数人在这里说过的话,但可能还有其他一些因素(比如担心在 append
重新分配切片后保持指向切片的指针)。
只是对这个自己很好奇。跑了一些基准:
type MyStruct struct {
F1, F2, F3, F4, F5, F6, F7 string
I1, I2, I3, I4, I5, I6, I7 int64
}
func BenchmarkAppendingStructs(b *testing.B) {
var s []MyStruct
for i := 0; i < b.N; i++ {
s = append(s, MyStruct{})
}
}
func BenchmarkAppendingPointers(b *testing.B) {
var s []*MyStruct
for i := 0; i < b.N; i++ {
s = append(s, &MyStruct{})
}
}
结果:
BenchmarkAppendingStructs 1000000 3528 ns/op
BenchmarkAppendingPointers 5000000 246 ns/op
要点:我们在纳秒级。对于小片来说可能可以忽略不计。但是对于数百万次操作来说,这是毫秒和微秒之间的差异。
顺便说一句,我尝试使用预先分配的切片(容量为 1000000)再次运行基准测试,以消除 append() 定期复制底层数组的开销。附加结构下降了 1000ns,附加指针根本没有改变。
您能否提供有关何时直接使用结构与何时使用指向结构的指针的一般准则?
不,这在很大程度上取决于您已经提到的所有其他因素。
唯一真正的答案是:基准测试并查看。每个案例都是不同的,当你有实际的时间可以使用时,世界上所有的理论都不会产生影响。
(也就是说,我的直觉是使用指针,可能还有一个 sync.Pool
来帮助垃圾收集器:http://golang.org/pkg/sync/#Pool)
与地图、切片、通道、函数和方法不同,结构变量是通过副本传递的,这意味着在后台分配了更多内存。另一方面,减少指针会减少垃圾收集器的工作量。从我的角度来看,我会更多地考虑三件事:结构复杂性、要处理的数据量以及创建 var 后的功能需求(当它被传递给函数时是否需要可变? ETC..)
BenchmarkAppendingStructs-8 5000000 387 ns/op BenchmarkAppendingPointers-8 3000000 422 ns/op