生産からの代表的なプロファイルの収集
あなたの生産環境は、アプリケーションの代表的なプロファイルの最良のソースです。これはプロファイルの収集で説明されています。
これを始める最も簡単な方法は、net/http/pprofをアプリケーションに追加し、サービスの任意のインスタンスから/debug/pprof/profile?seconds=30
を取得することです。これは素晴らしいスタート方法ですが、これが代表的でない可能性がある方法もあります:
- このインスタンスは、プロファイルを取得している瞬間には何もしていないかもしれませんが、通常は忙しいです。
- トラフィックパターンは日中に変化する可能性があり、日中の動作が変わることがあります。
- インスタンスは長時間実行される操作を行うことがあります(例:操作Aを5分間実行し、その後操作Bを5分間実行するなど)。30秒のプロファイルは、単一の操作タイプのみをカバーする可能性があります。
- インスタンスはリクエストの公平な分配を受けないかもしれません(あるインスタンスは他のインスタンスよりも特定のタイプのリクエストを多く受け取ります)。
より堅牢な戦略は、異なるインスタンスから異なる時間に複数のプロファイルを収集して、個々のインスタンスプロファイル間の違いの影響を制限することです。複数のプロファイルは、PGOで使用するためにマージされることがあります。
多くの組織は、これらの種類の艦隊全体のサンプリングプロファイリングを自動的に実行する「継続的プロファイリング」サービスを運営しており、これをPGOのプロファイルのソースとして使用することができます。
プロファイルのマージ
pprofツールは、次のように複数のプロファイルをマージできます:
$ go tool pprof -proto a.pprof b.pprof > merged.pprof
このマージは、プロファイルの壁の持続時間に関係なく、入力のサンプルの単純な合計です。その結果、アプリケーションの小さな時間スライスをプロファイルする場合(例:無限に実行されるサーバー)、すべてのプロファイルが同じ壁の持続時間を持つことを確認したいでしょう(つまり、すべてのプロファイルが30秒間収集されます)。そうでない場合、壁の持続時間が長いプロファイルは、マージされたプロファイルで過剰に表現されます。
AutoFDO
Go PGOは「AutoFDO」スタイルのワークフローをサポートするように設計されています。
プロファイルの収集で説明されているワークフローを詳しく見てみましょう:
- 1. 初期バイナリをビルドしてリリースします(PGOなし)。
- 2. 生産からプロファイルを収集します。
- 3. 更新されたバイナリをリリースする時が来たら、最新のソースからビルドし、生産プロファイルを提供します。
- 4. 2に戻る
これは一見単純に思えますが、ここで注意すべきいくつかの重要な特性があります:
- 開発は常に進行中であるため、バイナリのプロファイルされたバージョンのソースコード(ステップ2)は、ビルドされる最新のソースコード(ステップ3)とはわずかに異なる可能性があります。Go PGOはこれに対して堅牢であるように設計されており、これをソースの安定性と呼びます。
- これは閉じたループです。つまり、最初の反復の後、プロファイルされたバージョンのバイナリは、前の反復からのプロファイルで既にPGO最適化されています。Go PGOもこれに対して堅牢であるように設計されており、これを反復的安定性と呼びます。
ソースの安定性は、プロファイルからのサンプルをコンパイル中のソースに一致させるためのヒューリスティックを使用して達成されます。その結果、新しい関数を追加するなどのソースコードへの多くの変更は、既存のコードとの一致に影響を与えません。コンパイラが変更されたコードを一致させることができない場合、いくつかの最適化が失われますが、これは優雅な劣化であることに注意してください。一つの関数が一致しない場合、最適化の機会を失うかもしれませんが、全体的なPGOの利益は通常、多くの関数に分散されます。マッチングと劣化に関する詳細については、ソースの安定性セクションを参照してください。
反復的安定性は、連続するPGOビルドにおける変動パフォーマンスのサイクルを防ぐことです(例:ビルド#1は速い、ビルド#2は遅い、ビルド#3は速い、など)。CPUプロファイルを使用して、最適化のターゲットとなるホット関数を特定します。理論的には、ホット関数はPGOによって非常に速くなる可能性があり、次のプロファイルではホットでなくなり、最適化されず、再び遅くなる可能性があります。GoコンパイラはPGO最適化に対して保守的なアプローチを取り、これが重要な変動を防ぐと考えています。このような不安定性を観察した場合は、go.dev/issue/newに問題を報告してください。
ソースと反復的安定性を組み合わせることで、最初の未最適化ビルドがカナリアとしてプロファイルされ、その後PGOで生産用に再ビルドされるという二段階ビルドの要件が排除されます(絶対的なピークパフォーマンスが必要な場合を除く)。
ソースの安定性とリファクタリング
上記で説明したように、GoのPGOは古いプロファイルからのサンプルを現在のソースコードに一致させるために最善を尽くします。具体的には、Goは関数内の行オフセットを使用します(例:関数fooの5行目で呼び出し)。
多くの一般的な変更は、一致を壊さないでしょう。これには以下が含まれます:
- ホット関数の外のファイルの変更(関数の上または下にコードを追加/変更する)。
- 同じパッケージ内の別のファイルに関数を移動する(コンパイラはソースファイル名を完全に無視します)。
一致を壊す可能性のある変更もあります:
- ホット関数内の変更(行オフセットに影響を与える可能性があります)。
- 関数の名前変更(および/またはメソッドの型)(シンボル名が変更されます)。
- 関数を別のパッケージに移動する(シンボル名が変更されます)。
プロファイルが比較的最近のものであれば、違いはおそらく少数のホット関数にのみ影響を与え、マッチしない関数での最適化の見逃しの影響を制限します。それでも、コードはめったに元の形にリファクタリングされないため、劣化は時間とともに徐々に蓄積されるため、生産からのソースの偏りを制限するために新しいプロファイルを定期的に収集することが重要です。
プロファイルの一致が大幅に劣化する状況の一つは、多くの関数の名前を変更したり、パッケージ間で移動したりする大規模なリファクタリングです。この場合、新しいプロファイルが新しい構造を示すまで短期的なパフォーマンスの低下が発生する可能性があります。
単純な名前変更の場合、既存のプロファイルは理論的には古いシンボル名を新しい名前に変更するように書き換えられる可能性があります。github.com/google/pprof/profileには、このようにpprofプロファイルを書き換えるために必要なプリミティブが含まれていますが、執筆時点ではこれに対する市販のツールは存在しません。
新しいコードのパフォーマンス
新しいコードを追加したり、フラグの切り替えで新しいコードパスを有効にしたりすると、そのコードは最初のビルドのプロファイルには存在せず、新しいコードを反映した新しいプロファイルが収集されるまでPGO最適化を受けません。新しいコードの展開を評価する際には、初期リリースがその定常状態のパフォーマンスを表さないことを念頭に置いてください。
Go標準ライブラリパッケージをPGOで最適化することは可能ですか?
はい。GoのPGOはプログラム全体に適用されます。すべてのパッケージは、標準ライブラリパッケージを含む潜在的なプロファイルガイド最適化を考慮して再ビルドされます。
依存モジュール内のパッケージをPGOで最適化することは可能ですか?
はい。GoのPGOはプログラム全体に適用されます。すべてのパッケージは、依存関係内のパッケージを含む潜在的なプロファイルガイド最適化を考慮して再ビルドされます。これは、アプリケーションが依存関係を使用する独自の方法が、その依存関係に適用される最適化に影響を与えることを意味します。
代表的でないプロファイルを使用したPGOは、PGOなしよりもプログラムを遅くしますか?
そうではありません。生産の動作を代表しないプロファイルは、アプリケーションの冷たい部分で最適化をもたらしますが、アプリケーションのホットな部分を遅くすることはありません。PGOが無効にした場合よりもパフォーマンスが悪化するプログラムに遭遇した場合は、go.dev/issue/newに問題を報告してください。
異なるGOOS/GOARCHビルドに同じプロファイルを使用できますか?
はい。プロファイルの形式は、OSおよびアーキテクチャの構成間で同等であるため、異なる構成間で使用できます。たとえば、linux/arm64バイナリから収集されたプロファイルは、windows/amd64ビルドで使用できます。
とはいえ、上記で説明したソースの安定性の注意事項はここにも適用されます。これらの構成間で異なるソースコードは最適化されません。ほとんどのアプリケーションでは、コードの大部分はプラットフォームに依存しないため、この形式の劣化は制限されます。
具体的な例として、パッケージos
のファイル処理の内部は、LinuxとWindowsで異なります。これらの関数がLinuxプロファイルでホットである場合、Windowsの同等の関数はプロファイルと一致しないため、PGO最適化を受けません。
異なるGOOS/GOARCHビルドのプロファイルをマージすることができます。これを行う際のトレードオフについては、次の質問を参照してください。
異なるワークロードタイプに使用される単一のバイナリをどのように扱うべきですか?
ここには明白な選択肢はありません。異なるタイプのワークロード(例:あるサービスで読み取り重視で使用され、別のサービスで書き込み重視で使用されるデータベース)に使用される単一のバイナリは、異なるホットコンポーネントを持ち、異なる最適化の恩恵を受ける可能性があります。
3つのオプションがあります:
- 1. 各ワークロードのために異なるバージョンのバイナリをビルドします:各ワークロードからのプロファイルを使用して、バイナリの複数のワークロード特化ビルドを作成します。これにより、各ワークロードに対して最良のパフォーマンスが提供されますが、複数のバイナリとプロファイルソースを扱うことに関して運用の複雑さが増す可能性があります。
- 2. 「最も重要な」ワークロードからのプロファイルのみを使用して単一のバイナリをビルドします:「最も重要な」ワークロード(最大のフットプリント、最もパフォーマンスに敏感)を選択し、そのワークロードからのプロファイルのみを使用してビルドします。これにより、選択したワークロードに対して最良のパフォーマンスが提供され、他のワークロードに対しても、ワークロード間で共有される共通コードの最適化からの適度なパフォーマンス向上が期待できます。
- 3. ワークロード間でプロファイルをマージします:各ワークロードからのプロファイルを(総フットプリントで重み付けして)取得し、単一の「艦隊全体」のプロファイルにマージして、単一の共通プロファイルをビルドします。これにより、すべてのワークロードに対して適度なパフォーマンス向上が提供される可能性があります。
PGOはビルド時間にどのように影響しますか?
PGOビルドを有効にすると、パッケージのビルド時間が測定可能に増加する可能性があります。これの最も顕著な要素は、PGOプロファイルがバイナリ内のすべてのパッケージに適用されるため、プロファイルの最初の使用には依存関係グラフ内のすべてのパッケージの再ビルドが必要です。これらのビルドは他のビルドと同様にキャッシュされるため、同じプロファイルを使用した後続のインクリメンタルビルドは完全な再ビルドを必要としません。
ビルド時間が極端に増加する場合は、go.dev/issue/newに問題を報告してください。
注:コンパイラによるプロファイルの解析も、特に大きなプロファイルの場合、かなりのオーバーヘッドを追加する可能性があります。大きな依存関係グラフを持つ大きなプロファイルを使用すると、ビルド時間が大幅に増加する可能性があります。これはgo.dev/issue/58102で追跡されており、今後のリリースで対処される予定です。
PGOはバイナリサイズにどのように影響しますか?
PGOは、追加の関数インライン化により、バイナリがわずかに大きくなる可能性があります。
Goランタイムによって生成されたCPUプロファイル(runtime/pprofなどを介して)は、PGO入力として直接使用するための正しい形式になっています。ただし、組織は代替の好ましいツール(例:Linux perf)や、Go PGOと一緒に使用したい既存の艦隊全体の継続的プロファイリングシステムを持っている場合があります。
代替ソースからのプロファイルは、pprof形式に変換される場合、Go PGOで使用できます。これには以下の一般的な要件に従う必要があります:
- サンプルインデックスの1つは、タイプ/単位が「samples」/「count」または「cpu」/「nanoseconds」である必要があります。
- サンプルは、サンプル位置でのCPU時間のサンプルを表す必要があります。
- プロファイルはシンボル化されている必要があります(Function.nameが設定されている必要があります)。
- サンプルはインライン関数のスタックフレームを含む必要があります。インライン関数が省略されると、Goは反復的安定性を維持できなくなります。
- Function.start_lineが設定されている必要があります。これは関数の開始行番号です。つまり、
func
キーワードを含む行です。Goコンパイラはこのフィールドを使用してサンプルの行オフセットを計算します(Location.Line.line - Function.start_line
)。多くの既存のpprofコンバータはこのフィールドを省略することに注意してください。
注:Go 1.21以前では、DWARFメタデータは関数の開始行を省略します(DW_AT_decl_line
)。これにより、ツールが開始行を特定するのが難しくなる可能性があります。
特定のサードパーティツールのPGO互換性に関する追加情報については、Go WikiのPGOツールページを参照してください。