テック技報最新号へ →
コード

Go: -gcflags=-m でエスケープ解析の結果を見て、ヒープ割り当てを減らす

Go コンパイラは変数をスタックに置くかヒープに置くか(エスケープ解析)を自動で決める。go build -gcflags=-m でその判断理由を表示でき、ホットパスの不要なヒープ割り当て(=GC 負荷)を見つけられる。

@func25 · 投稿 2026-10-12
#go#performance#escape-analysis#gc#コンパイラ

見方

sh
go build -gcflags='-m' ./...        # 判断結果
go build -gcflags='-m -m' ./pkg/... # 理由まで詳しく(出力が多い)
go test -gcflags='-m' -run TestX ./pkg  # テスト対象だけ見るときも

出力例(意味):

  • moved to heap: x … 変数 x がヒープに移された
  • &T{...} escapes to heap … 構造体リテラルがヒープ割り当てになった
  • x does not escape … スタックで済んでいる
  • can inline f … インライン化された(インライン化でエスケープしなくなることもある)

ヒープに逃げやすい典型

  • ローカル変数のポインタを返す(return &v)。呼び出し元より長く生きるため。
  • interface{} / any に値を入れる(fmt.Println(x) の引数など)。
  • クロージャが外側の変数をキャプチャし、そのクロージャが関数の外へ出る(goroutine に渡す、返す)。
  • サイズがコンパイル時に分からないスライス(make([]byte, n) の n が変数)や、大きすぎる配列。
  • 値をポインタでチャネルやグローバル変数・フィールドに保存する。

使いどころ

  • まずベンチマーク(go test -bench . -benchmem の allocs/op)で割り当てが多い関数を見つけ、そこだけ -m で理由を確認する。全体を最適化しようとしない。
  • 小さい構造体はポインタでなく値で返す・受け取ると、スタックで済むことが多い。
  • バッファは呼び出し側で確保して渡す(func Read(buf []byte) 型の API)と割り当てを使い回せる。

注意

  • 判断はコンパイラのバージョンで変わる。Go を上げたら測り直す。
  • 「ヒープ=悪」ではない。読みやすさを犠牲にした最適化は、計測で効果が確認できた箇所に限る。

出典

  • @func25 2026-10-12(解説動画の紹介): @func25 · 2026-10-12
  • 動画: youtube.com (動画本編は未視聴。上記はコマンドと一般的な挙動を自分の言葉でまとめたもの)
X の元の投稿を開く