我有一个简单的基准来比较创建结构片和指向该结构片的指针的性能
package pointer
import (
"testing"
)
type smallStruct struct {
ID int
}
func newSmallStruct(id int) *smallStruct {
return &smallStruct{ID: id}
}
func BenchmarkSmallStructPointer(b *testing.B) {
for n := 0; n < b.N; n++ {
var slice = make([]*smallStruct, 0, 10000)
for i := 0; i < 10000; i++ {
t := newSmallStruct(n + i)
slice = append(slice, t)
}
}
}
func BenchmarkSmallStruct(b *testing.B) {
for n := 0; n < b.N; n++ {
var slice = make([]smallStruct, 0, 10000)
for i := 0; i < 10000; i++ {
t := newSmallStruct(n + i)
slice = append(slice, *t)
}
}
}
字符串
基准结果
go test -bench . -benchmem
goos: linux
goarch: amd64
pkg: test-project/pointer
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkSmallStructPointer-4 3121 328864 ns/op 161921 B/op 10001 allocs/op
BenchmarkSmallStruct-4 29218 48021 ns/op 81920 B/op 1 allocs/op
型
请解释一下,是什么操作产生了这么多的指针切片分配?它似乎是追加到切片,但我不明白为什么?
1条答案
按热度按时间q5iwbnjs1#
Go语言有一个名为“逃逸分析”的特性,它可以记录哪些值逃逸到堆中,以及为什么逃逸。
它通过一个可选详细程度的编译器标志启用:
-gcflags='-m=2'
。您可以在go test
,go build
,go run
上设置它。(任何将该标志沿着传递给go tool compile
的内容)使用这个我们可以看到
&smallStruct
逃逸到堆,强制分配。如果我正确解释结果的话,这是因为即使newSmallStruct
在两个测试用例中都是内联的,编译器也可以看到指针在第二种情况下立即被解引用。而第一种情况继续将指针传递给其他函数(append
):字符串
具有更高的详细度(3):
型
本演讲详细介绍了各种情况:https://www.youtube.com/watch?v=ZMZpH4yT7M0
围绕此优化
即使没有
append
-使用普通索引分配-这也会导致堆分配。这是因为如果
&smallStruct{...}
是在newSmallStruct()
的堆栈框架中分配的,那么该地址可能会在以后被覆盖。Go会重新使用旧的函数堆栈空间来创建新的函数。在这里,在堆中分配是要做的“可验证正确”的事情。如果你有一个情况下,你需要优化性能,首先创建一个只值的切片-理想情况下返回一个来自
newSmallStruct()
的值,这样它就可以在不内联的情况下工作。然后让指针切片引用这些本地值。的字符串