当golang为字符串到字节转换进行分配时

时间:2016-07-24 17:28:08

标签: go allocation

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

2 个答案:

答案 0 :(得分:2)

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

你可以在这里看到Dmitry Dvyukov的公关

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

不幸的是,从很久以前,当编译器是用C语言编写的时候,我不确定在当前编译器中哪里可以找到优化。但我可以确认它仍然存在,而德米特里的公关描述是准确的。

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

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

如果我们只查看第二个基准BenchmarkHashingWithNew900000000,我们可以看到它应该分配的明确位置。

bytStr := []byte(testString)

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

值得注意这个小技巧,因为如果您的基准字符串都很短,就很容易欺骗自己相信某些代码不会分配。

答案 1 :(得分:-1)

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

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