
こんにちは。よっしーです(^^)
本日は、Go言語のプロファイルガイド最適化ついて解説しています。
背景
Go言語でアプリケーションのパフォーマンスを改善したいと思ったことはありませんか? 公式ドキュメントで「Profile-guided optimization (PGO)」という機能を見かけたものの、英語で書かれていて専門用語も多く、「どういう仕組みなんだろう?」「自分のプロジェクトにどう活用すればいいの?」と戸惑った経験があるかもしれません。
この記事では、Go 1.21から正式サポートされたPGO(プロファイルガイド最適化)について、公式ドキュメントの丁寧な日本語訳と、初心者の方にもわかりやすい補足説明をお届けします。PGOは実行時のプロファイル情報をコンパイラにフィードバックすることで、2〜14%のパフォーマンス向上が期待できる強力な機能です。
「コンパイラ最適化」や「プロファイリング」と聞くと難しそうに感じるかもしれませんが、基本的な使い方はとてもシンプルです。この記事では、PGOの仕組みを身近な例えで説明し、実際にどうやって使うのかを具体的なコード例とともに紹介していきます。一緒に学んでいきましょう!
PGOを使用したビルド
標準的なアプローチは、プロファイル対象のバイナリのメインパッケージディレクトリに、default.pgoというファイル名でpprof CPUプロファイルを保存することです。デフォルトでは、go buildはdefault.pgoファイルを自動的に検出し、PGOを有効にします。
プロファイルをソースリポジトリに直接コミットすることが推奨されています。プロファイルは再現可能な(そしてパフォーマンスの高い!)ビルドにとって重要な入力であるためです。ソースと一緒に保存することで、ソースを取得する以外にプロファイルを取得するための追加のステップが不要になり、ビルド体験が簡素化されます。
より複雑なシナリオの場合、go build -pgoフラグがPGOプロファイルの選択を制御します。このフラグは、上述のdefault.pgoの動作のためにデフォルトで-pgo=autoに設定されています。フラグを-pgo=offに設定すると、PGO最適化が完全に無効になります。
default.pgoを使用できない場合(例えば、1つのバイナリに対して異なるシナリオ用の異なるプロファイルがある、プロファイルをソースと一緒に保存できないなど)、使用するプロファイルへのパスを直接渡すことができます(例:go build -pgo=/tmp/foo.pprof)。
注意:-pgoに渡されたパスは、すべてのメインパッケージに適用されます。例えば、go build -pgo=/tmp/foo.pprof ./cmd/foo ./cmd/barは、foo.pprofをfooとbarの両方のバイナリに適用しますが、これは多くの場合望ましくありません。通常、異なるバイナリには異なるプロファイルを使用すべきで、それぞれ別のgo build呼び出しを通じて渡す必要があります。
注意:Go 1.21より前では、デフォルトは-pgo=offです。PGOは明示的に有効にする必要があります。
解説
最もシンプルな使い方:default.pgoを置くだけ
PGOを使う最も簡単な方法は、プロファイルファイルを特定の場所に置くだけです。
ディレクトリ構造:
myapp/
├── main.go
├── default.pgo ← ここに置くだけ!
├── handler.go
└── go.mod
ビルドコマンド:
# 普通にビルドするだけでPGOが自動で有効になる
go build
# 何も特別なことをする必要なし!
default.pgoの作り方
前のセクションで収集したプロファイルを、default.pgoにリネームするだけです。
# 本番環境からプロファイルを取得
curl http://production:6060/debug/pprof/profile?seconds=30 > cpu.pprof
# ファイル名を default.pgo に変更
mv cpu.pprof default.pgo
# メインパッケージのディレクトリに配置
mv default.pprof ./cmd/myapp/
なぜソースリポジトリにコミットするの?
一見すると「バイナリファイルをGitにコミットするの?」と思うかもしれませんが、これには重要な理由があります。
理由1:再現可能なビルド
# チームメンバーAがビルド
git clone repo
go build
# → PGO最適化されたバイナリが生成される
# チームメンバーBがビルド(同じコミット)
git clone repo
go build
# → 全く同じバイナリが生成される
理由2:シンプルなワークフロー
# プロファイルが別の場所にある場合(複雑)
git clone repo
aws s3 cp s3://bucket/profiles/default.pgo ./cmd/myapp/
go build
# プロファイルがリポジトリにある場合(シンプル)
git clone repo
go build # これだけ!
理由3:バージョン管理
# プロファイルの変更履歴も追跡できる
git log default.pgo
commit abc123
Date: 2024-01-15
"Update profile after performance improvements"
commit def456
Date: 2024-01-10
"Add initial PGO profile"
ビルドフラグの使い方:3つのモード
モード1:自動モード(デフォルト、推奨)
# default.pgo があれば自動的にPGOを使用
go build
# 明示的に指定することも可能
go build -pgo=auto
モード2:無効モード
# PGOを完全に無効化(テスト用など)
go build -pgo=off
モード3:カスタムパス指定
# 特定のプロファイルを使用
go build -pgo=/path/to/custom.pprof
実践例:複数の環境用プロファイル
実際のプロジェクトでは、異なる環境や用途で異なるプロファイルを使いたい場合があります。
ディレクトリ構造:
myapp/
├── cmd/
│ └── myapp/
│ ├── main.go
│ └── default.pgo ← 本番環境用(デフォルト)
├── profiles/
│ ├── production.pgo ← 本番環境用(明示的)
│ ├── high-traffic.pgo ← 高トラフィック時用
│ └── batch-processing.pgo ← バッチ処理用
└── Makefile
Makefile での使い分け:
# デフォルト:本番環境用プロファイル
build:
go build -o myapp ./cmd/myapp
# 高トラフィック用に最適化
build-high-traffic:
go build -pgo=./profiles/high-traffic.pgo -o myapp ./cmd/myapp
# バッチ処理用に最適化
build-batch:
go build -pgo=./profiles/batch-processing.pgo -o myapp ./cmd/myapp
# PGOなしでビルド(比較用)
build-no-pgo:
go build -pgo=off -o myapp ./cmd/myapp
注意点1:複数バイナリへの適用
複数のバイナリを一度にビルドする場合は注意が必要です。
間違った例:1つのプロファイルを複数バイナリに適用
# これは foo と bar の両方に同じプロファイルを適用してしまう
go build -pgo=/tmp/foo.pprof ./cmd/foo ./cmd/bar
正しい例:それぞれ別々にビルド
# foo用のプロファイルで foo をビルド
go build -pgo=./profiles/foo.pprof ./cmd/foo
# bar用のプロファイルで bar をビルド
go build -pgo=./profiles/bar.pprof ./cmd/bar
なぜ別々にビルドする必要があるの?
異なるバイナリは異なる動作パターンを持つため、それぞれに適したプロファイルが必要です。
例:マイクロサービスアーキテクチャ
project/
├── cmd/
│ ├── api-server/
│ │ ├── main.go
│ │ └── default.pgo ← HTTPリクエスト処理に最適化
│ ├── worker/
│ │ ├── main.go
│ │ └── default.pgo ← バックグラウンドジョブに最適化
│ └── batch/
│ ├── main.go
│ └── default.pgo ← データ処理に最適化
各サービスは異なる処理を行うため:
- api-server: ネットワークI/O、JSON処理が頻繁
- worker: データベースクエリ、キュー処理が主
- batch: ファイルI/O、大量データ処理が中心
実践的なビルドスクリプト例
シンプルな例:Makefileを使った管理
.PHONY: build build-pgo update-profile
# 通常ビルド(PGO自動適用)
build:
go build -o bin/myapp ./cmd/myapp
# PGOを明示的に有効化
build-pgo:
go build -pgo=auto -o bin/myapp ./cmd/myapp
# 本番環境からプロファイルを更新
update-profile:
curl http://production:6060/debug/pprof/profile?seconds=30 > cmd/myapp/default.pgo
git add cmd/myapp/default.pgo
git commit -m "Update PGO profile from production"
CI/CDでの使用例:GitHub Actions
name: Build with PGO
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# default.pgo はリポジトリに含まれているので何もしなくてOK
- name: Build with PGO
run: go build ./cmd/myapp
- name: Upload binary
uses: actions/upload-artifact@v3
with:
name: myapp-pgo-optimized
path: myapp
バージョンによる違い:Go 1.21以降は自動有効
Go 1.21以降(現在):
# default.pgo があれば自動的にPGOが有効
go build # PGO有効
# 無効にしたい場合は明示的に指定
go build -pgo=off
Go 1.20以前:
# デフォルトではPGOが無効
go build # PGO無効
# 有効にするには明示的に指定が必要
go build -pgo=auto
トラブルシューティング:PGOが有効か確認する方法
方法1:ビルドログを確認
go build -v
# 出力に "PGO profile" などのメッセージがあればPGO有効
方法2:ベンチマークで比較
# PGOなしでビルド
go build -pgo=off -o myapp-no-pgo
# PGOありでビルド
go build -pgo=auto -o myapp-pgo
# パフォーマンスを比較
./myapp-no-pgo benchmark
./myapp-pgo benchmark
チェックリスト:PGOビルドのベストプラクティス
- ✅
default.pgoをメインパッケージディレクトリに配置 - ✅ プロファイルファイルをGitにコミット
- ✅ 定期的にプロファイルを更新(月1回など)
- ✅ 複数バイナリは個別にビルド
- ✅ CI/CDパイプラインでも同じプロファイルを使用
- ❌ 古いプロファイルを放置しない
- ❌ 開発環境のプロファイルを本番ビルドに使わない
- ❌ 複数バイナリに同じプロファイルを適用しない
まとめ
PGOを使ったビルドは驚くほどシンプルです。基本的には**default.pgoを置くだけ**で、あとは通常通りgo buildするだけです。プロファイルをソースリポジトリに含めることで、チーム全体が同じ最適化の恩恵を受けられ、ビルドの再現性も保証されます。まずは本番環境からプロファイルを取得し、default.pgoとして配置することから始めてみましょう。
おわりに
本日は、Go言語のプロファイルガイド最適化について解説しました。

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

コメント