🐿️

Go1.21で正式リリースされたPGOについて

2023/09/03に公開

2023年9月現在の最新バージョンGo1.21で正式リリースされたPGOについて調査・検証してみました。

https://go.dev/doc/go1.21#compiler

TL;DR

まだ発展段階なので無闇矢鱈に活用せず様子見で良さそう。

PGOについて

概要

PGO(Profile-guided optimization) とは、
実行時のプロファイリング結果を、次のビルドのためにコンパイラにフィードバックし、より多くの情報に基づいた最適化を行えるようにするコンパイラの最適化手法のこと。

例)頻繁に呼び出されるホット関数を積極的にインライン化することで、関数呼び出しのコストが減り、処理を高速化

現在のバージョンだとインライン最適化以外は明記されておらず、Go1.20のプレビュー時と同様、インライン最適化のみ。

以下のissueによるとGoの将来のバージョンで他の最適化についても追加される予定。

https://github.com/golang/go/issues/55022#issuecomment-1245605666


GoのPGOの特徴として、反復安定性があるため、反復的に性能改善を行うことができる(らしい)

らしいと含みを持たせたのは後述する。

ちなみに反復安定性とは↓

反復安定性とは、連続するPGOビルドで性能が変動するサイクル(ビルド1は高速、ビルド2は低速、ビルド3は高速など)を防ぐこと。

CPUプロファイルを使用して、最適化の対象となるホット関数を特定する。
理論的には、ホットな関数はPGOによって高速化され、次のプロファイルではホットでなくなり、最適化されずに再び遅くなる可能性がある。
Goコンパイラは、PGO最適化に対して保守的なアプローチをとっており、大きなばらつきを防いでいる。

https://go.dev/doc/pgo#:~:text=matching and degradation.-,Iterative stability,-is the prevention

使用方法

PGOへの入力として CPU pprof プロファイルを使用。

↓他のプロファイリングシステムのプロファイルを使用することもできる
https://go.dev/doc/pgo#alternative-sources

ワークフローは以下の通り:

  1. 初期バイナリ(PGOなし)をビルドしてリリース
  2. 本番環境からプロファイルを収集
  3. 更新されたバイナリをリリースするときは、本番環境プロファイルを使用して最新のソースからビルド
  4. 2に戻る

最良の結果を得るためには、プロファイルがアプリケーションの実稼働環境における実際の動作を代表するものであることが重要。
代表的でないプロファイルを使用すると、本番環境ではほとんど改善されないバイナリになる可能性が高い。
したがって、本番環境から直接プロファイルを収集することが推奨される。

本番環境から収集することが困難な場合は、代表的なベンチマークから収集しても良い。

計測結果

実行環境
macOS Ventura Version 13.4.1
Darwin Kernel Version 22.5.0
Go Version 1.21.0
メモリ 32GB

Profile-guided optimization previewのコードを元に計測を行なった。previewドキュメントによると2.6%くらい速くなるらしい。

nopgoはPGOを使用せずにビルドした場合。
withpgo<N>はprofile結果を何回反復的に使用したかを表している。
結果は、2回実行してその平均を取得。
"%表記"や"~"はBenchstatコマンドによる結果。

比べ元 比べ先 結果
nopgo withpgo ~
nopgo withpgo2 +4.2%
nopgo withpgo3 +0.5%
nopgo withpgo4 ~

+X%は処理速度が遅くなったことを意味し、"~"は統計的に有意な差を検出しなかったことを意味する。

PGOを使用してビルドした結果、有意な差がない or 遅くなっている

また反復安定性があるかどうか確認するために4回PGOによる最適化を行ったが、2回目の最適化で+4.2%となっており本当に反復安定性があると言っていいのかはわからない

ちなみにwithpgo同士の比較も行った。

Benchstatコマンドの実行結果メモ
一回目
benchstat nopgo.txt withpgo.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │  nopgo.txt  │          withpgo.txt          │
                │   sec/op    │   sec/op     vs base          │
GenerateLoad-10   93.86µ ± 3%   92.35µ ± 1%  ~ (p=0.429 n=20)

二回目
benchstat nopgo.txt withpgo.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │  nopgo.txt  │          withpgo.txt          │
                │   sec/op    │   sec/op     vs base          │
GenerateLoad-10   91.04µ ± 1%   90.87µ ± 0%  ~ (p=0.341 n=20)

====================================================
./markdown.withpgo.exeを元にdefault2.pgoを作成する
go build -pgo default2.pgo -o markdown.withpgo2.exe

一回目

benchstat nopgo.txt withpgo2.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │  nopgo.txt  │            withpgo2.txt            │
                │   sec/op    │   sec/op     vs base               │
GenerateLoad-10   90.93µ ± 0%   95.55µ ± 5%  +5.08% (p=0.000 n=20)

benchstat withpgo.txt withpgo2.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo.txt │            withpgo2.txt            │
                │   sec/op    │   sec/op     vs base               │
GenerateLoad-10   90.86µ ± 1%   95.55µ ± 5%  +5.16% (p=0.000 n=20)

二回目

benchstat nopgo.txt withpgo2.txt  
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │  nopgo.txt  │            withpgo2.txt            │
                │   sec/op    │   sec/op     vs base               │
GenerateLoad-10   90.93µ ± 0%   93.98µ ± 4%  +3.36% (p=0.000 n=20)

benchstat withpgo.txt withpgo2.txt                                                      
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo.txt │            withpgo2.txt            │
                │   sec/op    │   sec/op     vs base               │
GenerateLoad-10   90.86µ ± 1%   93.98µ ± 4%  +3.44% (p=0.000 n=20)

====================================================
./markdown.withpgo2.exeを元にdefault3.pgoを作成する
go build -pgo default3.pgo -o markdown.withpgo3.exe

一回目

benchstat nopgo.txt withpgo3.txt 
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │  nopgo.txt  │            withpgo3.txt            │
                │   sec/op    │   sec/op     vs base               │
GenerateLoad-10   90.93µ ± 0%   91.46µ ± 1%  +0.59% (p=0.006 n=20)

benchstat withpgo.txt withpgo3.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo.txt │            withpgo3.txt            │
                │   sec/op    │   sec/op     vs base               │
GenerateLoad-10   90.86µ ± 1%   91.46µ ± 1%  +0.66% (p=0.005 n=20)

benchstat withpgo2.txt withpgo3.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo2.txt │            withpgo3.txt            │
                │    sec/op    │   sec/op     vs base               │
GenerateLoad-10    93.98µ ± 4%   91.46µ ± 1%  -2.68% (p=0.003 n=20)

二回目

benchstat nopgo.txt withpgo3.txt                                                     
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │  nopgo.txt  │            withpgo3.txt            │
                │   sec/op    │   sec/op     vs base               │
GenerateLoad-10   90.93µ ± 0%   91.21µ ± 1%  +0.32% (p=0.033 n=20)

benchstat withpgo.txt withpgo3.txt                                                     
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo.txt │            withpgo3.txt            │
                │   sec/op    │   sec/op     vs base               │
GenerateLoad-10   90.86µ ± 1%   91.21µ ± 1%  +0.39% (p=0.028 n=20)

withpgo2.txt withpgo3.txt                                                     
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo2.txt │            withpgo3.txt            │
                │    sec/op    │   sec/op     vs base               │
GenerateLoad-10    93.98µ ± 4%   91.21µ ± 1%  -2.95% (p=0.001 n=20)

====================================================
./markdown.withpgo3.exeを元にdefault4.pgoを作成する
go build -pgo default4.pgo -o markdown.withpgo4.exe

一回目

benchstat nopgo.txt withpgo4.txt 
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │  nopgo.txt  │         withpgo4.txt          │
                │   sec/op    │   sec/op     vs base          │
GenerateLoad-10   90.93µ ± 0%   90.90µ ± 1%  ~ (p=0.841 n=20)

benchstat withpgo.txt withpgo4.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo.txt │         withpgo4.txt          │
                │   sec/op    │   sec/op     vs base          │
GenerateLoad-10   90.86µ ± 1%   90.90µ ± 1%  ~ (p=0.718 n=20)

benchstat withpgo2.txt withpgo4.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo2.txt │            withpgo4.txt            │
                │    sec/op    │   sec/op     vs base               │
GenerateLoad-10    93.98µ ± 4%   90.90µ ± 1%  -3.28% (p=0.000 n=20)


benchstat withpgo3.txt withpgo4.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo3.txt │         withpgo4.txt          │
                │    sec/op    │   sec/op     vs base          │
GenerateLoad-10    91.21µ ± 1%   90.90µ ± 1%  ~ (p=0.096 n=20)

二回目

benchstat nopgo.txt withpgo4.txt  
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │  nopgo.txt  │         withpgo4.txt          │
                │   sec/op    │   sec/op     vs base          │
GenerateLoad-10   90.93µ ± 0%   90.61µ ± 1%  ~ (p=0.068 n=20)

benchstat withpgo.txt withpgo4.txt                                                     
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo.txt │         withpgo4.txt          │
                │   sec/op    │   sec/op     vs base          │
GenerateLoad-10   90.86µ ± 1%   90.61µ ± 1%  ~ (p=0.242 n=20)

benchstat withpgo2.txt withpgo4.txt
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo2.txt │            withpgo4.txt            │
                │    sec/op    │   sec/op     vs base               │
GenerateLoad-10    93.98µ ± 4%   90.61µ ± 1%  -3.59% (p=0.000 n=20)

benchstat withpgo3.txt withpgo4.txt                                                     
goos: darwin
goarch: arm64
pkg: example.com/markdown/load
                │ withpgo3.txt │            withpgo4.txt            │
                │    sec/op    │   sec/op     vs base               │
GenerateLoad-10    91.21µ ± 1%   90.61µ ± 1%  -0.66% (p=0.001 n=20)
  • PGOを使用しても改善されない?
  • 反復安定性があると言っていいのかわからない

所感

previewと言えど、正式にGoから出ているドキュメントの内容を試した結果、ドキュメントとは全く違う結果が得られて若干困惑している。
また反復安定性を謳いつつも2回目の最適化では性能が急に落ちている点も気になる。

PGOによるコンパイラ最適化は現在のバージョンだとまだ強いメリットを感じない。
しかし今後インライン最適化以外の最適化手法も導入されるみたいなので将来に期待

参考

PGOのドキュメント
https://go.dev/doc/pgo

PGOのプレビュードキュメント
https://go.dev/blog/pgo-preview

PGO Flagのドキュメント
https://pkg.go.dev/cmd/go#:~:text=go test respectively.-,-pgo file,-specify the file

Discussion