Escape Analysis and Stack Allocation on Go lang 1.1

最近、Go言語にいろんな意味ではまっているので、調べたメモ。

結果が色々まちがってるので、Escape Analysis and Stack Allocation on Go lang 1.2 レシーバ編 - fousの日記をみてください。

Introduction

Go言語の特徴として、スタック領域ヒープ領域を区別しないという特徴がある。
そのため、C言語初心者がやるような、こんなプログラムを書いてもよい。

package main
import "fmt"

func main() {
	var a1 *int = call1()
	fmt.Printf("value = %d", *a1)
}

func call1() *int {
	var b1 int = 10
	return &b1
}

とはいえこれは、プログラマが明示的には区別しないというだけで、実際にはコンパイラがコードを解析し割り当て先を決めている。 ref: http://golang.jp/go_faq#stack_or_heap
一般に、変数はスタックに割り当てたほうが高速に動作する。確保も解放も速く、キャッシュに乗りやすい(たぶん)し、GCによって回収する必要もないからだ。
では、どのような変数がスタックに割り当てられるのか?
素直に考えると、↑のコードの`b1`はヒープに割り当てられるように見える。ポインタa1によってmain関数からb1を参照するためには、b1がcall1関数のスタック内にあるとダメだからだ。
が、実際はもうちょっと複雑である。Goのコンパイラはわりとかしこいので、インライン化エスケープ解析をしてくれる。
↑のコードのコンパイル中の動作を-mオプションで出力すると、こうなる。

go build -o /dev/null -gcflags -m main.go 2>&1| tee main
# command-line-arguments
./main.go:9: can inline call1
./main.go:5: inlining call to call1
./main.go:5: call1 &b1 does not escape
./main.go:6: main ... argument does not escape
./main.go:10: moved to heap: b1
./main.go:11: &b1 escapes to heap

main関数内ではcall1が展開され(インライン化)、b1がmain関数のスタックから外に出ないことが確認できた(エスケープ解析)結果、b1はスタックに割り当てられている。
一方、call1関数そのものは、b1がcall1関数のスタックから外に出ていることが解析されたため、b1をヒープに割り当てている。

という長い前振りは置いておいて、どういう条件ならスタックに割り当てられるのか?をいくつか実験してみたのでその結果。 コードはこのへん https://github.com/t-yuki/goalloctest

Analysis1

Source: analysis1.gopkg1/pkg1.go -> Result

Analysis1では、int型の引数と返り値をもつ、別パッケージの関数Max1/Max2/Max3をmainから呼び出している。それぞれの詳細はSourceを参照。

Resultをみてわかる通り、別パッケージであってもインライン化している。C++で、ヘッダファイルで宣言したclass中でメソッドを定義したときの挙動に近い。
ただしインライン化しているのはMax1関数だけで、他の関数を呼び出しているMax2/Max3はインライン化されていない。
解析の深さの制限があるのかもしれないが、ひとまずここまで。

Analysis2

Source: analysis2.gopkg2/pkg2.go -> Result

Analysis2では、int型の引数と返り値をもつ、レシーバーつきの別パッケージの関数Max1/Max2/Max3/Max4/Max5を、呼び出し方を変えながらmainから呼び出している。それぞれの詳細はSourceを参照。

Resultの要約としては、

1. newで確保しようが、変数宣言で確保しようが関係ない
2. Exportedだろうがなかろうが関係ない
3. receiverがあろうがなかろうが関係ない
4. interfaceとして扱うとHEAP

Declaration Max1 Max2 Max3 Max4 Max5 Max6 Max7
var c1 pkg2.MaxerImpl STACK STACK STACK STACK STACK STACK STACK
d1 := new(pkg2.MaxerImpl) STACK STACK STACK STACK STACK STACK STACK
e1 := pkg2.NewMaxerImpl() STACK STACK STACK STACK STACK STACK STACK
f1 := pkg2.NewMaxer() HEAP HEAP HEAP HEAP HEAP HEAP HEAP