优化堆分配

huangapple go评论71阅读模式
英文:

Optimizing away heap allocations

问题

当我谈到Go时,我指的是gc编译器的实现。

据我所知,Go执行逃逸分析。在Go代码中经常看到以下习惯用法:

func NewFoo() *Foo

逃逸分析会注意到Foo逃逸了NewFoo,并在堆上分配Foo。

这个函数也可以这样写:

func NewFoo(f *Foo)

并且可以这样使用:

var f Foo
NewFoo(&f)

在这种情况下,只要f没有逃逸到其他地方,f可以在栈上分配。

现在是我真正的问题。

编译器是否可以将每个foo() *Foo优化为foo(f *Foo),甚至可能在每个级别上都返回Foo?

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

提前谢谢。

英文:

When I am talking about Go, I am speaking about the gc compiler implementation.

As far as I know, Go performs escape analysis.
The following idiom is seen pretty often in Go code:

func NewFoo() *Foo

Escape analysis would notice that Foo escapes NewFoo and allocate Foo on the heap.

This function could also be written as:

func NewFoo(f *Foo)

and would be used like

var f Foo
NewFoo(&f)

In this case, as long as f doesn't escape anywhere else, f could be allocated on the stack.

Now to my actual question.

Would it be possible for the compiler to optimize every foo() *Foo into foo(f *Foo), even possibly over multiple levels where Foo is returned in each?

If not, in what kind of cases does this approach fail?

Thank you in advance.

答案1

得分: 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()
}

在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

因此,它将NewFoo内联到F1中,再将F1内联到main中(并表示如果有人调用它,它还可以进一步内联main)。
尽管它确实说在NewFoo本身中&Foo逃逸了,但在内联时它不会逃逸。

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

当然,这只是一个非常简单的例子,任何复杂情况(例如使用f调用fmt.Print)都可能导致逃逸。
一般来说,除非分析表明您有问题区域,并且您正在尝试优化特定的代码部分,否则不必过于担心这个问题。
习惯用法和可读性的代码应该优先于优化。

还请注意,使用go test -bench -benchmem(或最好使用testing.BReportAllocs方法)可以报告基准代码的分配情况,这有助于识别出比预期/期望更多分配的情况。

英文:

(Not quite an answer but too big for a comment.)

From the comments it seems you might be interested in this small example:

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()
}

On Go1.5 running go build -gcflags="-m" gives:

./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

So it inlines NewFoo into F1 into main (and says that it could further inline main if someone was to call it).
Although it does say that in NewFoo itself &Foo escapes, it does not escape when inlined.

The output from go build -gcflags="-m -S" confirms this with a main initializing the object on the stack and not doing any function calls.

Of course this is a very simple example and any complications (e.g. calling fmt.Print with f) could easily cause it to escape.
In general, you shouldn't worry about this too much unless profiling has told you that you have a problem area(s) and you are trying to optimize a specific section of code.
Idiomatic and readable code should trump optimization.

Also note that using go test -bench -benchmem (or preferably using testing.B's ReportAllocs method) can report on allocations of benchmarked code which can help identify something doing more allocations than expected/desired.

答案2

得分: 2

在进行了更多的研究后,我找到了我要找的内容。

我所描述的显然被称为“返回值优化”,而且在Go语言中也是可行的,这基本上回答了我对于是否在Go语言中也可能实现这一点的问题。

关于此的更多信息可以在这里找到:
https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization

英文:

After doing some more research I found what I was looking for.

What I was describing is apparently called "Return value optimization" and is well doable, which pretty much answers my question about whether this was possible in Go as well.

Further information about this can be found here:
https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization

huangapple
  • 本文由 发表于 2015年8月24日 06:45:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/32172287.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定