【问题标题】:When golang does allocation for string to byte conversion当 golang 分配字符串到字节的转换时
【发布时间】:2017-08-23 18:06:28
【问题描述】:
var testString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
//var testString = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"
func BenchmarkHashing900000000(b *testing.B){
    var bufByte = bytes.Buffer{}
    for i := 0; i < b.N ; i++{
        bufByte.WriteString(testString)
        Sum32(bufByte.Bytes())
        bufByte.Reset()
    }
}

func BenchmarkHashingWithNew900000000(b *testing.B){
    for i := 0; i < b.N ; i++{
        bytStr := []byte(testString)
        Sum32(bytStr)
    }
}

测试结果:

With  testString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
BenchmarkHashing900000000-4         50000000            35.2 ns/op         0 B/op          0 allocs/op
BenchmarkHashingWithNew900000000-4  50000000            30.9 ns/op         0 B/op          0 allocs/op

With testString = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"
BenchmarkHashing900000000-4         30000000            46.6 ns/op         0 B/op          0 allocs/op
BenchmarkHashingWithNew900000000-4  20000000            73.0 ns/op        64 B/op          1 allocs/op

为什么在BenchmarkHashingWithNew900000000的情况下,字符串长时有分配,而字符串小时没有分配。
Sum32 : https://gowalker.org/github.com/spaolacci/murmur3
我正在使用 go1.6

【问题讨论】:

    标签: go allocation


    【解决方案1】:

    您的基准测试正在观察 Golang 编译器(版本 1.8)的奇怪优化。

    您可以在此处查看 Dmitry Dvyukov 的 PR

    https://go-review.googlesource.com/c/go/+/3120

    不幸的是,那是很久以前,当编译器是用 C 编写的时,我不确定在当前编译器中哪里可以找到优化。但我可以确认它仍然存在,而且 Dmitry 的 PR 描述是准确的。

    如果您想要一套更清晰的自包含基准来证明这一点,我在这里有一个要点。

    https://gist.github.com/fmstephe/f0eb393c4ec41940741376ab08cbdf7e

    如果我们只看第二个基准BenchmarkHashingWithNew900000000,我们可以清楚地看到它“应该”分配的位置。

    bytStr := []byte(testString)
    

    此行必须将testString 的内容复制到一个新的[]byte 中。但是在这种情况下,编译器可以看到bytStrSum32 返回后不再使用。因此它可以在堆栈上分配。但是,由于字符串可以任意大,分配的堆栈的限制设置为 32 个字节 string[]byte

    注意这个小技巧是值得的,因为如果你的基准字符串都很短,很容易让自己相信某些代码没有分配。

    【讨论】:

      【解决方案2】:

      当您将内容写入byte.Buffer 时,它会根据需要分配内存。当您调用byte.Buffer.Reset 时,该内存不会被释放,而是保留以供以后重用。而您的代码正是这样做的。它将缓冲区标记为空,然后再次填充。

      实际上, 有一些分配正在进行,但是当迭代 50000000 次时,它可以忽略不计。但是如果你将bufByte 的声明移动到for 循环中,你会得到一些分配。

      【讨论】:

      • 这是我所期望的。除了上面的基准标记显示较小的字符串外,这两种情况都没有分配。但对于更大的字符串,它的行为符合预期。
      • 你应该看看编译后的代码。 go test -c 然后go tool objdump -s 'BenchmarkHashing' main.test.exe
      • @VishalKumar 您实际上不应该看到bytes.Buffer 版本在either 情况下的分配。 Bytes.Buffer 在其结构中具有内置的 64 字节数组,用于处理无需(额外)分配的小型数据集,并且这些测试均不超过每次写入操作的 64 字节。如果将 bufByte.Reset() 移到 for 循环之外,我感觉您会看到更公平的结果。
      • @Kaedys 问题不在于为什么没有分配 bytes.Buffer。循环外将有单一分配。结果符合预期。问题是为什么不分配给字符串较小的其他情况。
      • 想一想,我有一种预感,您正在看到编译器优化,tbh。尝试将 bytStr 切片中的值分配给另一个全局切片,以防止编译器对其进行优化。只要您分配给已定义的全局切片变量(NOT 附加到全局切片),就不应为该分配进行任何额外分配,只需填充指针和设置切片头中的一对整数。示例:play.golang.org/p/FchGznDWNC
      猜你喜欢
      • 2019-09-18
      • 2016-01-12
      • 1970-01-01
      • 1970-01-01
      • 2018-08-06
      • 1970-01-01
      • 2022-12-03
      • 1970-01-01
      • 2014-01-25
      相关资源
      最近更新 更多