Slide 1

Slide 1 text

CI/CD に PGO を組み込んだ話 Shota Iwamatsu (@shota8511_tech) layerx.go #1

Slide 2

Slide 2 text

自己紹介 Shota Iwamatsu LayerX 2023/8~ バクラク事業部 カード開発グループ ソフトウェアエンジニア ex. Infcurion, Latona, Accenture Go 歴3年くらい @shota8511_tech

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

今日のトピック 1. PGO とは 2. 実際に導入してみる 3. 導入してどうなったか

Slide 5

Slide 5 text

Profile-Guided Optimization (PGO) とは コンパイラ最適化手法の1つ。 ビルド時に、コンパイラにランタイム情報 (プロファイル) を与えることで、より実際のワークロードに即し た最適化を行うことができる。 平たく言えば、ソースコードの変更無しで、パフォーマンスを向上できるビルド方法のこと。 Go に限らず様々な言語で提供されている。Feedback-Directed Optimization (FDO) とも呼ばれている。

Slide 6

Slide 6 text

例えば

Slide 7

Slide 7 text

関数のインライン展開 コンパイラ最適化の1つ。 関数呼び出しを関数本体に置き換える。 func main() { a := 1 b := 2 c := sum(a, b) fmt.Println(c) } func sum(a, b int) int { return a + b }

Slide 8

Slide 8 text

関数のインライン展開 コンパイラ最適化の1つ。 関数呼び出しを関数本体に置き換える。 func main() { a := 1 b := 2 c := a + b // 関数呼び出しが関数本体に置き換えられた。 fmt.Println(c) } func sum(a, b int) int { return a + b }

Slide 9

Slide 9 text

何が嬉しい?

Slide 10

Slide 10 text

インライン展開のメリット 1. 関数呼び出しのオーバーヘッドがなくなる 関数呼び出しは、余分な CPU 命令 (オーバーヘッド) が発生する。 このオーバーヘッドは通常ナノ秒レベルだが、積み重なることでパフォーマンスに影響が出る。 インライン展開により関数呼び出しが不要になるので、このオーバーヘッドがなくなる。 2. さらなるコンパイラ最適化が可能になる インライン展開することで、他の最適化を適用できるようになる。 定数畳み込みやエスケープ解析など。

Slide 11

Slide 11 text

ただしデメリットもある バイナリサイズが増加する。 ビルド時間が長くなる。 → 通常のビルド時は、単純な関数のみに適用するよう対象を制限している。

Slide 12

Slide 12 text

とはいえ… 頻繁に呼び出される関数は、多少のデメリットを許容してでも、パフォーマンスを向上させたい。 しかしコンパイラは、実際のワークロードにおいて、どの関数が頻繁に呼び出されるのか分からない。 → プロファイルを与えることで、それを加味した最適化ができるようになる!

Slide 13

Slide 13 text

Go で PGO を適用する方法 Go 1.21 で正式リリースされた。 main パッケージに default.pgo という名前で CPU プロファイルを配置すれば、自動的に適用される。 go build -pgo=/tmp/foo.pprof で任意のファイルを指定することも可能。 プロファイルは pprof のフォーマットに則っている必要がある。 公式ドキュメント曰く 2~14% のパフォーマンス向上が見込める。

Slide 14

Slide 14 text

実際に導入してみる

Slide 15

Slide 15 text

どのようにプロファイルを収集するか PGO で良い結果を得るには、実際のワークロードに近いプロファイルを収集できるかどうかが重要。 基本的には本番環境のプロファイルを取得することが推奨されている。 net/http/pprof を使えば取得できるが、考えないといけないことが多い。 どこからリクエストを送る? いつ・どのくらいの期間で取得する? 取得時に例外的に負荷が少ない状態だったら?

Slide 16

Slide 16 text

継続的プロファイラーを使う 常時プロファイルを収集し、定期的に管理用のサーバーに送信してくれる。 必要な時に任意のプロファイルを DL することができる。 各社が提供しているサービスや OSS が存在する。 Datadog: Continuous Profiler LayerX ではオブザーバビリティに Datadog を使用しているため、今回はこちらを採用。 Google Cloud: Cloud Profiler Grafana: Grafana Pyroscope

Slide 17

Slide 17 text

Datadog Continuous Profiler プロファイルの一覧から、任意のプロファイルを DL できる。 フレームグラフを表示したり、プロファイル同士を比較したりもできる。

Slide 18

Slide 18 text

Datadog Continuous Profiler Datadog がライブラリを提供している。 既に Datadog を利用している場合は簡単に導入できる。 import ( "gopkg.in/DataDog/dd-trace-go.v1/profiler" ) func main() { err := profiler.Start( profiler.WithService(""), profiler.WithEnv(""), profiler.WithVersion(""), // ... ) if err != nil { log.Fatal(err) } defer profiler.Stop() // ... }

Slide 19

Slide 19 text

どのように CI/CD に組み込むか PGO を CI/CD に組み込むうえで、プロファイルは一度取得して終わりではない。 ソースコードは常に変化するので、プロファイルも直近のものを使うのが望ましい。 ビルドの度に毎回手動でプロファイルを DL してコミットするのは面倒。 自動化して CI/CD に組み込みたい。

Slide 20

Slide 20 text

datadog-pgo を使う Datadog が提供している、プロファイルを DL する CLI ツール。 直近72時間で CPU 使用量が大きいプロファイルを5個選出し、マージしてくれる。 時間と個数はコマンドライン引数で調整可能。 go build の実行前に datadog-pgo を実行するだけで、CI/CD に PGO を組み込める。 export DD_API_KEY=xxx export DD_APP_KEY=xxx go run github.com/DataDog/datadog-pgo@latest 'service:foo env:prod' ./cmd/foo/default.pgo # datadog-pgo により default.pgo が作成されているので、ビルド時に PGO が適用される。 go build ./cmd/foo/...

Slide 21

Slide 21 text

導入してどうなったか

Slide 22

Slide 22 text

CPU 使用率が平均13.5%減少した! メトリクスの計算式: ((CPU 使用率 / 1週間前の CPU 使用率) -1) * 100 PGO 適用後3日間について、上記メトリクスの平均値が-13.5%だった。

Slide 23

Slide 23 text

メモリ使用率も平均9.5%減少した! メトリクスの計算式: ((メモリ使用率 / 1週間前のメモリ使用率) -1) * 100 PGO 適用後3日間について、上記メトリクスの平均値が-9.5%だった。

Slide 24

Slide 24 text

ただし… パフォーマンス向上の大部分は gopkg.in/DataDog/dd-trace-go.v1/profiler だった。 PGO によってプロファイラーが最適化されるという、マッチポンプ的な結果になってしまった…。 とはいえプロファイラーはパフォーマンス分析に欠かせないので、パフォーマンス向上は嬉しい。 実際のエンドポイントに限定すると、約5%のパフォーマンス向上。 1分あたりの CPU 使用時間が約4ミリ秒短縮された。 劇的な改善ではないが、導入の手軽さを考慮すると十分な結果。

Slide 25

Slide 25 text

その他の留意点 バイナリサイズが増加する。 今回は0.1%以下の増加だった。 ビルド時間が長くなる。 今回は誤差の範疇だったため正確な値は不明。

Slide 26

Slide 26 text

まとめ PGO により、ソースコードの変更無しで 2~14% のパフォーマンス向上が見込める。 プロファイルの収集は、継続的プロファイラーを導入すると便利。 datadog-pgo を使うと、簡単に PGO を CI/CD に組み込める。