优化堆分配

sqr*_*pin -2 optimization go

当我谈论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在每个级别中返回?

如果不是,这种方法在哪种情况下会失败?

先感谢您.

Dav*_*e C 6

(不是一个答案,但对评论来说太大了.)

从评论看来,您可能对这个小例子感兴趣:

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)

因此,内联NewFooF1main(并说,它可以内嵌进一步main,如果有人是称呼它).虽然它确实说它NewFoo本身&Foo逃脱了,但它在内联时并没有逃脱.

输出通过初始化堆栈上的对象并且不执行任何函数调用来go build -gcflags="-m -S"确认这一点main.

当然,这是一个很简单的例子,任何的并发症(如调用fmt.Printf)很容易导致其逃脱.一般情况下,除非分析告诉您有问题区域并且您正在尝试优化特定的代码段,否则您不应该太担心这一点.惯用语和可读代码应该胜过优化.

另请注意,使用go test -bench -benchmem(或最好使用testing.B's ReportAllocs方法)可以报告基准代码的分配,这有助于识别比预期/期望更多分配的内容.