关于go:性能:切片结构与结构指针切片

Performance: Slices of structs vs. slices of pointers to structs

我经常使用结构切片。下面是这样一个结构的示例:

1
2
3
4
5
type MyStruct struct {
    val1, val2, val3    int
    text1, text2, text3 string
    list                []SomeType
}

所以我定义我的切片如下:

1
[]MyStruct

假设我有大约一百万个元素在里面,我正在大量地处理这个切片:

  • 我经常附加新元素。(元素总数未知。)
  • 我偶尔会整理一下。
  • 我还删除了元素(尽管没有添加新元素那么多)。
  • 我经常读元素并传递它们(作为函数参数)。
  • 元素本身的内容不会改变。
  • 小精灵

    我的理解是,这会导致实际结构的大量混乱。另一种方法是创建指向结构的指针切片:

    1
    []*MyStruct

    现在结构仍然保持在它们所在的位置,我们只处理指针,我认为指针占用空间较小,因此将使我的操作更快。但现在我要给垃圾回收工更多的工作。

    • 您能否提供有关何时直接使用结构与何时使用指向结构的指针的一般准则?
    • 我应该担心我留给GC多少工作吗?
    • 复制结构与复制指针的性能开销是否可以忽略?
    • 也许一百万个元素不多。当切片变得更大时(当然仍然适合RAM),所有这些是如何改变的?
    • 小精灵


      我自己也很好奇。运行了一些基准:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      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{})
          }
      }

      结果:

      1
      2
      BenchmarkAppendingStructs  1000000        3528 ns/op
      BenchmarkAppendingPointers 5000000         246 ns/op

      注意:我们在纳秒之内。对于小片来说可能可以忽略不计。但对于数以百万计的操作来说,这是毫秒和微秒之间的差别。

      顺便说一句,我尝试使用预先分配的切片(容量为1000000)再次运行基准测试,以消除定期复制底层数组的append()带来的开销。追加结构减少了1000ns,追加指针根本没有改变。


      Can you provide general guidelines of when to work with structs directly vs. when to work with pointers to structs?

      不,这在很大程度上取决于你已经提到的所有其他因素。

      唯一真正的答案是:基准和观察。每种情况都是不同的,当你有实际的工作时间时,世界上所有的理论都不会有什么不同。

      (也就是说,我的直觉是使用指针,可能还有一个sync.Pool来帮助垃圾收集器:http://golang.org/pkg/sync/pool)