
こんにちは。よっしーです(^^)
本日は、Go言語のパフォーマンス分析ついて解説しています。
背景
Go言語でアプリケーションを開発していると、必ずと言っていいほど直面するのがパフォーマンスの問題や予期しないバグです。「なぜこんなにメモリを消費しているのか?」「どこで処理が遅くなっているのか?」「このゴルーチンはなぜデッドロックしているのか?」—— こうした疑問に答えるために、Goのエコシステムには強力な診断ツール群が用意されています。
しかし、これらのツールは種類が多く、それぞれ異なる目的と特性を持っているため、どのような場面でどのツールを使うべきか迷ってしまうことも少なくありません。プロファイリング、トレーシング、デバッギング、ランタイム統計…それぞれのツールが解決できる問題は異なり、時には相互に干渉し合うこともあります。
本記事では、Go公式ドキュメントの診断ツールに関する解説を日本語で紹介しながら、実際の開発現場でどのように活用できるかを探っていきます。適切なツールを選択し、効果的に問題を診断することで、より高品質で高性能なGoアプリケーションの開発が可能になるはずです。特に、パフォーマンスのボトルネックを特定し改善することは、ユーザー体験の向上やインフラコストの削減に直結する重要なスキルと言えるでしょう。
プロファイリング
プロファイリングは、コードの中でコストが高い部分や頻繁に呼び出される部分を特定するのに有用です。Goランタイムは、pprofビジュアライゼーションツールが期待する形式でプロファイリングデータを提供します。プロファイリングデータは、go testによるテスト中、またはnet/http/pprofパッケージから提供されるエンドポイント経由で収集できます。ユーザーはプロファイリングデータを収集し、pprofツールを使用して主要なコードパスをフィルタリングし可視化する必要があります。
runtime/pprofパッケージによって提供される事前定義済みのプロファイル:
- cpu: CPUプロファイルは、プログラムがCPUサイクルを能動的に消費している間(スリープやI/O待機中とは対照的に)どこで時間を費やしているかを特定します。
- heap: ヒーププロファイルは、メモリ割り当てのサンプルを報告します。現在および過去のメモリ使用状況を監視し、メモリリークをチェックするために使用されます。
- threadcreate: スレッド作成プロファイルは、新しいOSスレッドの作成につながるプログラムの部分を報告します。
- goroutine: ゴルーチンプロファイルは、現在のすべてのゴルーチンのスタックトレースを報告します。
- block: ブロックプロファイルは、ゴルーチンが同期プリミティブ(タイマーチャネルを含む)で待機してブロックしている場所を表示します。ブロックプロファイルはデフォルトで有効になっていません。有効にするには
runtime.SetBlockProfileRateを使用してください。 - mutex: ミューテックスプロファイルは、ロックの競合を報告します。ミューテックスの競合によりCPUが完全に活用されていないと思われる場合、このプロファイルを使用してください。ミューテックスプロファイルはデフォルトで有効になっていません。有効にするには
runtime.SetMutexProfileFractionを参照してください。
プロファイリングとは?
プロファイリングは、プログラムの「健康診断書」を作る作業です。人間ドックで体の各部位を詳しく検査するように、プログラムのどこに問題があるかを詳細に調べます。
プロファイリングデータの収集方法
方法1: テスト時に収集
# テスト実行時にCPUプロファイルを取得
go test -cpuprofile=cpu.prof -bench=.
# プロファイル結果を確認
go tool pprof cpu.prof
方法2: 実行中のプログラムから収集
import _ "net/http/pprof" // これを追加するだけ
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// メインの処理...
}
ブラウザで http://localhost:6060/debug/pprof/ にアクセスすると診断データが見られます。
6種類のプロファイル詳解
1. CPUプロファイル 🖥️
例え: マラソンランナーがどの区間で最も体力を使っているか調べる
// この関数がCPUを大量に使っているかもしれない
func calculatePrime(n int) bool {
for i := 2; i < n; i++ { // ← CPUプロファイルで「ここが遅い!」と分かる
if n%i == 0 {
return false
}
}
return true
}
使いどころ:
- プログラムが遅い
- CPU使用率が高い
- 計算処理を最適化したい
2. ヒーププロファイル 💾
例え: 部屋の中で何が一番スペースを取っているか調べる
func memoryLeak() {
var bigData [][]byte
for {
// メモリを確保し続ける(解放しない)
bigData = append(bigData, make([]byte, 1024*1024)) // 1MB
// ← ヒーププロファイルで「ここでメモリが増え続けている!」と分かる
}
}
使いどころ:
- メモリ使用量が増え続ける
- Out of Memoryエラーが出る
- メモリリークを探したい
3. スレッド作成プロファイル 🧵
例え: 会社で新しい部署がどこでできているか調べる
func createManyThreads() {
for i := 0; i < 1000; i++ {
go func() {
// OSスレッドを作成する可能性がある処理
runtime.LockOSThread() // ← ここでOSスレッドが作られる
// 処理...
}()
}
}
使いどころ:
- OSのスレッド数が異常に多い
- システムリソースを使い果たしている
4. ゴルーチンプロファイル 🚦
例え: 今どの信号で車が止まっているか一覧を見る
func main() {
for i := 0; i < 100; i++ {
go worker(i) // 100個のゴルーチンを作成
}
// ゴルーチンプロファイルで全ゴルーチンの状態が見える
}
使いどころ:
- ゴルーチンリークの調査
- デッドロックの原因調査
- 並行処理の状況確認
5. ブロックプロファイル 🚧
例え: 渋滞でどの交差点で車が止まっているか調べる
// まず有効化が必要
runtime.SetBlockProfileRate(1)
func blockedOperation() {
ch := make(chan int)
go func() {
time.Sleep(5 * time.Second)
ch <- 1
}()
<-ch // ← ここで5秒間ブロックされる!プロファイルで検出可能
}
使いどころ:
- プログラムが応答しない
- 処理が詰まっている場所を特定したい
- チャネル操作の最適化
6. ミューテックスプロファイル 🔒
例え: トイレの前で待っている人の列を調べる
// まず有効化が必要
runtime.SetMutexProfileFraction(1)
var mu sync.Mutex
var sharedResource int
func competingAccess() {
mu.Lock()
defer mu.Unlock()
// ← 他のゴルーチンがここで待たされる
time.Sleep(100 * time.Millisecond)
sharedResource++
}
使いどころ:
- CPUは空いているのに処理が遅い
- 並行処理でパフォーマンスが出ない
- ロックの競合を減らしたい
実践的な使い方のヒント
- 問題の症状から適切なプロファイルを選ぶ
- 遅い → CPUプロファイル
- メモリが増える → ヒーププロファイル
- 応答しない → ブロックプロファイル
- pprofの基本コマンド
# トップ10の関数を表示 go tool pprof -top cpu.prof # インタラクティブモード go tool pprof cpu.prof (pprof) top10 # 上位10個表示 (pprof) list main # main関数のソースコード表示 (pprof) web # ブラウザでグラフ表示 - 本番環境での注意点
- プロファイリング自体がパフォーマンスに影響する
- 短時間だけ有効にして、必要なデータを取得したらすぐ無効化
- ブロックプロファイルとミューテックスプロファイルは特に重い
プロファイリングをマスターすることで、パフォーマンスの問題を科学的に解決できるようになります!
おわりに
本日は、Go言語のパフォーマンス分析について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント