エスケープ解析分析
エスケープ解析は、Go コンパイラーが値をスタックではなくヒープに配置する理由を理解するのに役立ちます。 ヒープ割り当てはメモリ使用量を増やし、ガベージコレクターの負担を増やす可能性があります。
GoLand は go build -gcflags="-m=2 -json=0,<path>" コマンドを使用してエスケープ解析を実行し、コンパイラー診断を構造化された JSON フォーマットで保存します。 GoLand はコマンドの出力を処理し、その結果をコードに反映します。 これにより、コンパイラーメッセージへの移動、エスケープフローの確認、エディターで最適化の判断を理解することができます。
なぜ値がヒープに逃げるのか
スタックはゴルーチンごとに割り当てられたメモリー領域で、割り当てが安価かつ関数が戻ると自動的に解放されます。 ヒープは共有される寿命の長いメモリで、ガーベジコレクターがトラックしてクリーンアップしなければならないため、よりコストがかかります。 関数が戻った後も、その値を外部から必要とされる可能性があるとコンパイラーが証明できない場合、その値はヒープに逃げます。
エスケープの判断は、コードの構造や Go のバージョン、ターゲットプラットフォーム、さらにインライン化などその他の最適化の判断によって変わる場合があります。 ほとんどのエスケープは少数のパターンに集約されます:
ポインターの返却
ローカル値へのポインターを返すことは、エスケープの最も一般的な原因のひとつです。 その値は関数内で作成されますが、呼び出し側が関数の戻り後も参照を保持するため、その値は解放されるスタックフレーム上に存在できません。
このパターンは Go で安全かつ慣用的です。 値がエスケープするかどうかに関わらず、API の明確さのためにはポインターを返すのが適切なことが多いです。
クロージャとゴルーチン
クロージャまたはゴルーチンでキャプチャされた変数は、それらが生成元の関数の寿命を超えて残る可能性があるときにエスケープします。 コンパイラーはキャプチャされた値がまだ到達可能だとみなさなければならないため、ヒープ上に割り当てます。
インターフェースと動的な値
具象値をインターフェース経由で渡すと、ヒープ割り当てにつながる場合があります。 これはフォーマット、ロギング、インターフェースベースの API で最もよく見られ、値が interface{} (any )としてボックス化されてから処理されます。
値をインターフェース経由で渡しても、必ずしもヒープ割り当てが発生するわけではありません。 インターフェースの境界は、避けるのではなくエスケープ解析の出力で確認すべきポイントです。
スライス、マップ、構造体
現在の関数より長く生きるデータ構造に格納されたときに値はエスケープします。 もしポインターがマップ、スライス、構造体フィールドなどに格納され、そのコンテナが関数より長く生きる場合、格納された値も同様に長く存在しなければなりません。
すべてのエスケープが問題になるわけではありません。 どんな複雑さを持つプログラムでも、ヒープ上で生き続けなければならない値が存在します。エスケープ解析はホットパス上の割り当て調査に最も役立ち、すべてのヒープ割り当てをなくすためのものではありません。
エスケープ解析を実行する
ファイルやパッケージごとにエスケープ解析を実行できます。
ファイルまたはパッケージのエスケープ解析を実行する
メインメニューから を選択します。
Go の最適化 ツールウィンドウで、 エスケープ解析分析 を選択します。
スコープを選択してください:
ファイル: 選択したファイルを解析します。
パッケージを実行: 選択したパッケージを解析します。
表示したいエスケープ解析メッセージを選択します。
(オプション)Go プロセスの環境変数を指定します。
たとえば追加のコンパイラーフラグを渡すには
GOFLAGSを設定します。最適化を無効化する-Nや関数インライン化を無効化する-lなどがあります。GOARCHおよびGOOS変数も出力に影響することがあります。インライン化や割り当てなど一部のコンパイラー判断はターゲット依存だからです。エスケープ解析の実行 をクリックしてください。

エディターで結果を表示する
解析が完了すると、GoLand はエディターのガターにエスケープ解析メッセージを表示します。
複数のメッセージが同じ行に属する場合、ガターマーカーはこの行のログ数を表示します。例えば、 2 件のログ。

ガターメッセージを確認する
ガターマーカーにカーソルを合わせます。
ポップアップでコンパイラーメッセージとエスケープフローを確認します。

関数名にカーソルを合わせると、その関数のエスケープ解析結果を表示できます。
結果からコードへ移動する
GoLand は Go の最適化 ツールウィンドウにもエスケープ解析結果を表示します。
ツールウィンドウ内の結果をクリックすると、エディターの該当行へ移動します。
コード内の結果へ移動する
メインメニューから を選択します。
Go の最適化 ツールウィンドウで、キャッチしたいイベントを選択し、 エスケープ解析の実行 をクリックします。
結果タブでファイル、関数、またはメッセージカテゴリを展開します。
結果をクリックします。
GoLand はエディターの該当行にキャレットを移動します。
結果をフィルターおよびグループ化する
エディターおよびツールウィンドウでエスケープ解析メッセージをフィルターできます。
エスケープ解析メッセージをフィルターする
エスケープ解析を実行した後 、結果タブで ログをフィルタリングする をクリックします。
表示したいメッセージタイプを選択します。
結果ビューを変更する
View をクリックしてください。
ビューのいずれかを選択してください:
ツリー: ファイル、関数、メッセージタイプごとにグループ化された結果を表示します。

コンソール出力:
go build -gcflags="-m=2 -json=0,<path>"コマンドの生出力を表示します。 このビュー内の結果をクリックすると、該当行へ移動できます。
結果をグループ化する
グループ化 をクリックしてください。
GoLand で結果をどのようにグループ化するかを選択してください(例:関数単位やカテゴリ単位)。
エスケープ解析結果を比較
コードを変更したら、エスケープ解析を再実行して割り当てが実際にヒープから移動したかどうか確認してください。 各実行は Go の最適化 ツールウィンドウの個別のタブで開かれるため、変更前後の結果をタブ間で比較できます。
一般的なエスケープ解析メッセージを理解する
エスケープ解析は、値がスタックに割り当てられるか、ヒープに割り当てられる必要があるかを判定します。
多くのヒープ割り当ては必要であり、パフォーマンス上の問題ではありません。 エスケープ解析はベンチマークやプロファイリング結果と組み合わせて使い、パフォーマンスに影響する割り当てを特定してください。
メッセージタイプ | 説明 |
|---|---|
ヒープへのエスケープ | 値が現在の関数の戻り値で必要となるためヒープに割り当てるべきであることを示します。
func create() *int {
x := 10
return &x
}
この例では、関数が |
ヒープへ移動済み | 関数の戻り後に値が不要になると保証できないため、コンパイラーが値をヒープに割り当てたことを示します。
func process() {
value := 42
use(&value)
}
この例では、 |
パラメーターのリーク | 関数パラメーターが関数から逃げ、関数の戻り後も有効である必要があることを示します。
func store(p *int) *int {
return p
}
この例では、関数がパラメーター |
インライン化可能 | 関数が十分に小さく単純なため、コンパイラーが関数呼び出しを関数本体で置き換えられることを示します。
func add(a, b int) int {
return a + b
}
この例では、関数はシンプルな処理を含み、小さな本体を持っています。 コンパイラーは関数呼び出しのオーバーヘッドを減らし、最適化の機会を増やすために関数をインライン化できます。 |
呼び出しのインライン化 | コンパイラーが関数呼び出しをコンパイル時に関数本体で置換したことを示します。
func add(a, b int) int {
return a + b
}
func main() {
_ = add(1, 2)
}
この例では、コンパイラーが インライン化可能 は関数がインライン化の対象であることを示します。 呼び出しのインライン化 はコンパイラーが特定の関数呼び出しを実際にインライン化したことを示します。 |
その他 | エスケープ解析や最適化の判断に関連する追加のコンパイラー診断を表示します。 |