1. 導入
Go言語はガベージコレクション(GC)を備えた言語ですが、メモリの確保(アロケーション)が頻発すると、GCの動作頻度が増え、結果としてアプリケーションのレイテンシやCPU使用率が悪化します。多くのエンジニアはメモリサイズ(alloc_space)を気にしますが、実は「小さなオブジェクトが大量に生成されている」ケースこそ、GC負荷の隠れた原因となります。本記事では、pprofのalloc_objectsオプションを活用し、メモリ確保の「回数」に注目したパフォーマンスチューニング手法を解説します。
2. 基礎知識
Goのメモリ管理において、ヒープに確保されるデータには「サイズ」と「回数」の2つの側面があります。
alloc_spaceは確保されたバイト数を示し、メモリリークの調査に有効です。
一方で、alloc_objectsは、これまで何回メモリ確保が行われたかを示す指標です。
たとえ小さなオブジェクトであっても、それがループ内などで数百万回生成されれば、GCはその都度メモリの状態を追跡する必要があり、パフォーマンスを大きく低下させます。
3. 実装/解決策
メモリ確保の回数を分析するには、以下の手順でpprofを使用します。
1. プログラムに net/http/pprof をインポートする。
2. アプリケーションを動かし、一定時間のプロファイルを取得する。
3. go tool pprof コマンドに –alloc_objects オプションを付与して解析する。
これにより、サイズではなく「確保回数が多い関数」を特定できます。特定の関数で確保回数が異常に多ければ、そこが最適化の対象となります。
4. サンプルプログラム
以下のコードは、効率の悪いメモリ確保の例です。これを実行し、pprofで解析することで「なぜ確保回数が多いのか」を特定できます。
package main
import (
"log"
"net/http"
_ "net/http/pprof" // pprofを有効化
)
// 頻繁に呼び出されるとアロケーションを量産する関数
func generateSmallObjects() {
for i := 0; i < 1000000; i++ {
// 小さな構造体を都度生成して破棄する
// これが積み重なるとGC負荷が急増する
_ = struct{ a int }{a: i}
}
}
func main() {
go func() {
// ローカルで解析用サーバーを起動
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// 解析用にループ実行
for {
generateSmallObjects()
}
}
解析コマンド:
go tool pprof --alloc_objects http://localhost:6060/debug/pprof/allocs
(pprofに入った後、`top`コマンドを打つと、確保回数が多い順に関数が表示されます)
5. 応用・注意点
現場での活用法:
alloc_objectsで特定した箇所は、オブジェクトプール(sync.Pool)の導入や、変数の再利用、構造体のポインタ渡しから値渡しへの変更(エスケープ解析の回避)によって、劇的に改善できることが多いです。
注意点:
・最適化のしすぎに注意:可読性を犠牲にしてまで小さな確保を減らす必要はありません。あくまで「GC負荷がボトルネックになっている場合」にのみ適用してください。
・エスケープ解析の確認:`go build -gcflags="-m"` を併用することで、なぜその変数がヒープに確保されているのかを論理的に追うことができ、より確実なチューニングが可能です。

コメント