当我谈论Go时,我正在谈论gc编译器实现.
据我所知,Go执行逃逸分析.Go代码中经常出现以下习语:
func NewFoo() *Foo
Run Code Online (Sandbox Code Playgroud)
转义分析会注意到Foo转义NewFoo并在堆上分配Foo.
这个函数也可以写成:
func NewFoo(f *Foo)
Run Code Online (Sandbox Code Playgroud)
并将被用作
var f Foo
NewFoo(&f)
Run Code Online (Sandbox Code Playgroud)
在这种情况下,只要f不在其他地方转义,就可以在堆栈上分配f.
现在来看我的实际问题.
编译器是否可以优化每个foo() *Foo进程foo(f *Foo),甚至可能在多个级别进行优化,其中Foo在每个级别中返回?
如果不是,这种方法在哪种情况下会失败?
先感谢您.
(不是一个答案,但对评论来说太大了.)
从评论看来,您可能对这个小例子感兴趣:
package main
type Foo struct {
i, j, k int
}
func NewFoo() *Foo {
return &Foo{i: 42}
}
func F1() {
f := NewFoo()
f.i++
}
func main() {
F1()
}
Run Code Online (Sandbox Code Playgroud)
在Go1.5上运行go build -gcflags="-m"给出:
./new.go:7: can inline NewFoo
./new.go:11: can inline F1
./new.go:12: inlining call to NewFoo
./new.go:16: can inline main
./new.go:17: inlining call to F1
./new.go:17: inlining call to NewFoo
./new.go:8: &Foo literal escapes to heap
./new.go:12: F1 &Foo literal does not escape
./new.go:17: main &Foo literal does not escape
Run Code Online (Sandbox Code Playgroud)
因此,内联NewFoo成F1成main(并说,它可以内嵌进一步main,如果有人是称呼它).虽然它确实说它NewFoo本身&Foo逃脱了,但它在内联时并没有逃脱.
输出通过初始化堆栈上的对象并且不执行任何函数调用来go build -gcflags="-m -S"确认这一点main.
当然,这是一个很简单的例子,任何的并发症(如调用fmt.Print同f)很容易导致其逃脱.一般情况下,除非分析告诉您有问题区域并且您正在尝试优化特定的代码段,否则您不应该太担心这一点.惯用语和可读代码应该胜过优化.
另请注意,使用go test -bench -benchmem(或最好使用testing.B's ReportAllocs方法)可以报告基准代码的分配,这有助于识别比预期/期望更多分配的内容.