【问题标题】:Slices of structs vs. slices of pointers to structs结构切片与指向结构的指针切片
【发布时间】:2015-02-21 16:43:51
【问题描述】:

我经常使用结构切片。以下是此类结构的示例:

type MyStruct struct {
    val1, val2, val3    int
    text1, text2, text3 string
    list                []SomeType
}

所以我将切片定义如下:

[]MyStruct

假设我有大约一百万个元素,我正在大量使用切片:

  • 我经常添加新元素。 (元素总数未知。)
  • 我不时对其进行排序。
  • 我还删除了元素(虽然不如添加新元素那么多)。
  • 我经常阅读元素并将它们传递(作为函数参数)。
  • 元素本身的内容不会改变。

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

[]*MyStruct

现在结构保持在原来的位置,我们只处理我认为占用空间较小的指针,因此会使我的操作更快。但现在我要给垃圾收集器做更多的工作。

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

【问题讨论】:

  • 您的示例结构是 12 个单词(每个 int 1 个,每个字符串 2 个,切片 3 个),指针是 1。这是我最关心的删除,因为平均每个都需要移位, 数组的一半。如果您可以通过将一个元素与切片中的最后一个元素交换并将切片缩小 1 或通过将结构字段或指针归零来删除一个元素,那么这些将是常量时间。如果结构较大并且您对数组做了很多工作,我的直觉也是指针。
  • FWIW,at the bottom here are some considerations for choosing between []T and []*T-- 大多数人在这里所说的重新hash,但也许还有其他一些因素(比如担心在append 重新分配切片后保持指向切片的指针)。
  • 感谢您的这些提示。最后的讨论(通过@twotwotwo)特别有用,因为它列出了需要注意的常见场景和陷阱。

标签: performance go slice


【解决方案1】:

我自己只是对此感到好奇。跑了一些基准测试:

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,附加指针根本没有改变。

【讨论】:

  • 我更进一步(除了预分配列表)并附加了带有随机数据的非空结构,点比结构慢约 10%:BenchmarkAppendingStructs-8 5000000 387 ns/op BenchmarkAppendingPointers-8 3000000 422 ns/op
  • 所以没有使用指针切片的弊端,为什么我看不到他们比结构切片更频繁地使用它?
  • 你如何对这样的方法进行基准测试?只是计时?
  • Golang 工具包内置了一个基准测试功能。上面的“Benchmark***”函数被 go test 识别为基准测试。请参阅 Golang 文档。
  • 基准测试揭示了在直接结构上使用指针的直接好处 - 但如何衡量长期 GC 影响?
【解决方案2】:

您能否提供有关何时直接使用结构以及何时使用指向结构的指针的一般准则?

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

唯一真正的答案是:基准测试并查看。每个案例都是不同的,当你有实际的时间可以使用时,世界上所有的理论都不会产生影响。

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

【讨论】:

    【解决方案3】:

    与地图、切片、通道、函数和方法不同,结构变量是通过副本传递的,这意味着在后台分配了更多内存。另一方面,减少指针会减少垃圾收集器的工作量。从我的角度来看,我会更多地考虑三件事:结构复杂性、要处理的数据量以及创建 var 后的功能需求(当它被传递给函数时是否需要可变?等等。)

    【讨论】:

      猜你喜欢
      • 2015-07-04
      • 1970-01-01
      • 2015-05-01
      • 1970-01-01
      • 2021-01-18
      • 2019-11-10
      • 2018-01-20
      相关资源
      最近更新 更多