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.go と pkg1/pkg1.go -> Result
Analysis1では、int型の引数と返り値をもつ、別パッケージの関数Max1/Max2/Max3をmainから呼び出している。それぞれの詳細はSourceを参照。
Resultをみてわかる通り、別パッケージであってもインライン化している。C++で、ヘッダファイルで宣言したclass中でメソッドを定義したときの挙動に近い。
ただしインライン化しているのはMax1関数だけで、他の関数を呼び出しているMax2/Max3はインライン化されていない。
解析の深さの制限があるのかもしれないが、ひとまずここまで。
Analysis2
Source: analysis2.go と pkg2/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 |