Goコンパイラはエスケープ解析(ポインタの移動分析)メカニズムを適用します:デフォルトではすべての値はスタックに配置されますが、変数がスコープを「離れる」(例えば、ローカル変数へのポインタが返される場合)と、自動的にヒープに配置されます。
これはパフォーマンスにとって重要です:
コンパイラはオブジェクトをスタックに配置できるかどうかを判断しようとしますが、関数の境界を「生き抜く」場合 — ヒープに置きます。
エスケープの例:
func NewPoint() *int { a := 42 return &a // ヒープにエスケープ! }
エスケープしない例:
func Sum(a, b int) int { c := a + b return c // スタックに }
コンパイラの診断には go build -gcflags='-m' を使用して詳細を確認してください:
go build -gcflags='-m' main.go
「関数から唯一値を返す場合、オブジェクトはヒープに行くのか — ポインタではなく?」
多くの人は、返される値は「ヒープに行く」と考えています。実際には、値(ポインタではない)が返される場合、変数はスタックに留まる可能性があります。
例:
func F() int { x := 10 return x // スタックアロケーション、ヒープには行かない }
ストーリー
ローカル変数へのポインタを返すファクトリ関数を通じて構造体を大量に作成した結果、メモリ使用量とGCへの負荷が急増しました。すべてのオブジェクトはローカル変数へのポインタの返却によりヒープに行ってしまい、値を返すことで回避できたことが判明しました。
ストーリー
マイクロサービスでは、ゴルーチン間でポインタを渡し、さまざまな関数から返すことにより、多くのオブジェクトがスタックからヒープに「流出」し、動作が遅くなり、GCの頻繁な中断を引き起こしました。
ストーリー
開発者は静的配列を関数内でスライスにラップし、スライスへのポインタを返しました — 負荷テスト中にメモリリークが発生し始めました。診断は、変数が関数の境界を「出ていない」のに、Goが間接的な使用のために誤ってヒープに配置することを決定したことを示しました。