バックグラウンド
Go バージョン 1.20 は 2023 年 2 月に正式リリースされ、このバージョンでは PGO パフォーマンス最適化メカニズムが導入されました。
PGO の正式な英語名は Profile Guided Optimization で、基本原則は次の 2 つのステップに分かれています。
- まずプログラムでプロファイリングを実行し、プログラムの実行中にデータを収集し、プロファイリング ファイルを生成します。
- プログラムをコンパイルするときに PGO オプションを有効にすると、コンパイラーは .pgo ファイルの内容に従ってプログラムのパフォーマンスを最適化します。
プログラムをコンパイルするとき、コンパイラーは、よく知られているインライン最適化 (インライン最適化)、エスケープ分析 (エスケープ分析)、定数伝播 (定数伝播) など、プログラムに対して多くの最適化を実行することは誰もが知っています。これらは、コンパイラーがプログラムのソース コードを分析することによって直接実装できる最適化です。
ただし、一部の最適化はソース コードを解析するだけでは実現できません。
たとえば、関数内には if/else の条件分岐判定が多数あり、コンパイラが条件分岐の順序を自動的に最適化して、条件分岐の判定を高速化し、プログラムのパフォーマンスを向上させることが期待できます。
ただし、これはプログラムの入力に関係するため、コンパイラは、どの条件分岐がより頻繁に入力され、どの条件分岐がより頻繁に入力されないかを知ることができない場合があります。
現時点では、コンパイラの最適化を行う人は PGO (Profile Guided Optimization) を思い浮かべます。
PGO の原理は非常に単純です。つまり、最初にプログラムを実行し、プログラムの実行中にデータを収集します。次に、コンパイラーは、収集されたプログラム実行時データに基づいてプログラムの動作を分析し、目標のパフォーマンスの最適化を実行します。
たとえば、プログラムはどの条件分岐がより多く入ったかを収集し、条件分岐の判断を前に置くことができます。これにより、時間のかかる条件判断が削減され、プログラムのパフォーマンスが向上します。
では、Go 言語はプログラムのパフォーマンスを最適化するためにどのように PGO を使用するのでしょうか? 次に具体的な例を見てみましょう。
例
/render
マークダウン ファイルのバイナリ形式を入力として受け取り、マークダウン形式を HTML 形式に変換して返すWeb インターフェイスを実装します。
このインターフェースはgitlab.com/golang-commonmark/markdown
プロジェクト。
環境構築
$ go mod init example.com/markdown
次のコードで新しいmain.go
ファイル。
package main
import (
"bytes"
"io"
"log"
"net/http"
_ "net/http/pprof"
"gitlab.com/golang-commonmark/markdown"
)
func render(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
return
}
src, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("error reading body: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
md := markdown.New(
markdown.XHTMLOutput(true),
markdown.Typographer(true),
markdown.Linkify(true),
markdown.Tables(true),
)
var buf bytes.Buffer
if err := md.Render(&buf, src); err != nil {
log.Printf("error converting markdown: %v", err)
http.Error(w, "Malformed markdown", http.StatusBadRequest)
return
}
if _, err := io.Copy(w, &buf); err != nil {
log.Printf("error writing response: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}
func main() {
http.HandleFunc("/render", render)
log.Printf("Serving on port 8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
プログラムをコンパイルして実行します。
$ go mod tidy
$ go build -o markdown.nopgo
$ ./markdown.nopgo
2023/02/25 22:30:51 Serving on port 8080...
プログラムのメイン ディレクトリに新しいファイルを作成しますinput.md
。コンテンツはカスタマイズ可能で、マークダウン構文に準拠する必要があるだけです。
マークダウン ファイルinput.md は、私が示した例で使用されています。
マークダウン ファイルのバイナリ コンテンツを、curl コマンドを通じてインターフェイスに送信します/render
。
$ curl --data-binary @input.md http://localhost:8080/render
<h1>The Go Programming Language</h1>
<p>Go is an open source programming language that makes it easy to build simple,
reliable, and efficient software.</p>
...
インターフェイスがinput.md
ファイルのコンテンツに対応する html 形式を返すことがわかります。
プロファイリング
次に、main.go
プログラムのプロファイリングを実行し、プログラムの実行時にデータを取得して、PGO を通じてパフォーマンスの最適化を実行します。
には、インポートnet/http/pprofmain.go
ライブラリがあり、元の既存の Web インターフェイスに基づいて新しい Web インターフェイスを追加します。プログラムの実行中にデータを取得するプロファイリング インターフェイスをリクエストできます。/render
/debug/pprof/profile
-
プログラムのメイン ディレクトリの下に、load サブディレクトリを追加します。loadサブディレクトリの下に新しく追加された
main.go
ファイルは、プログラムの実際の実行状況をシミュレートするために、load/main.go
上で./markdown.nogpo
起動したサーバーのインターフェイスを継続的に要求します。/render
$ go run example.com/markdown/load
-
プロファイリング インターフェイスにプログラム実行時データを取得するよう要求します。
$ curl -o cpu.pprof "http://localhost:8080/debug/pprof/profile?seconds=30"
30 秒ほど待つと、curl コマンドが終了し、プログラムのメイン ディレクトリにファイルが生成されますcpu.pprof
。
注: プログラムをコンパイルして実行するには、Go バージョン 1.20 を使用する必要があります。
PGO オプティマイザー
$ mv cpu.pprof default.pgo
$ go build -pgo=auto -o markdown.withpgo
go build
プログラムをコンパイルするときに、-pgo
このオプションを有効にします。
-pgo
指定されたプロファイリング ファイルとauto
パターンの両方をサポートできます。
auto
パターンの場合は、default.pgo
プログラムのメイン ディレクトリで指定されたプロファイリング ファイルが自動的に検索されます。
Goでは、プロジェクトのすべての開発者がauto
このモードを使用してプログラムのパフォーマンスを最適化できるように、このモードを使用し、メンテナンスのためにファイルをプログラムのメイン ディレクトリに保存することを公式に推奨しています。default.pgo
default.pgo
Go のバージョン 1.20 では、-pgo
オプションのデフォルト値は ですoff
。PGO 最適化を有効にするには、これを追加する必要があります-pgo=auto
。
-pgo
将来の Go バージョンでは、オプションのデフォルト値を に設定することが正式な計画ですauto
。
性能比較
プログラムのサブディレクトリに新しいファイルload
を追加し、 Go パフォーマンス テスト ベンチマーク フレームワークを使用してサーバーのストレス テストを行います。bench_test.go
bench_test.go
PGO 最適化が有効になっていないシナリオ
PGO 最適化を有効にせずにサーバー プログラムを有効にします。
$ ./markdown.nopgo
ストレス テストを開始します。
$ go test example.com/markdown/load -bench=. -count=20 -source ../input.md > nopgo.txt
PGO 最適化を有効にするシナリオ
PGO 最適化を有効にしてサーバー プログラムを有効にします。
$ ./markdown.withpgo
ストレス テストを開始します。
$ go test example.com/markdown/load -bench=. -count=20 -source ../input.md > withpgo.txt
総合的な比較
パフォーマンスの比較には、上記のストレス テストから得られたnopgo.txt
合計を使用します。withpgo.txt
$ go install golang.org/x/perf/cmd/benchstat@latest
$ benchstat nopgo.txt withpgo.txt
goos: darwin
goarch: amd64
pkg: example.com/markdown/load
cpu: Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz
│ nopgo.txt │ withpgo.txt │
│ sec/op │ sec/op vs base │
Load-4 447.3µ ± 7% 401.3µ ± 1% -10.29% (p=0.000 n=20)
PGO 最適化を使用した後、プログラムのパフォーマンスが 10.29% 向上したことがわかります。これは非常に印象的です。
Go バージョン 1.20 では、PGO を使用すると、プログラムのパフォーマンスが通常約 2% ~ 4% 向上します。
後続のバージョンでは、コンパイラは引き続き PGO メカニズムを最適化し、プログラムのパフォーマンスをさらに向上させます。
要約する
Go バージョン 1.20 では、コンパイラーがプログラムのパフォーマンスを最適化できるように PGO が導入されました。PGO は 2 つのステップで使用されます。
- まずプロファイリング ファイルを取得します。
- コンパイル時に PGO オプションを有効にし
go build
、プロファイリング ファイルを通じてプログラムのパフォーマンスを最適化するようにコンパイラーをガイドします。
運用環境では、最新のプロファイリング データを収集し、PGO を使用してプログラムを最適化し、システムの処理パフォーマンスを向上させることができます。
PGO に関する詳細な手順とベスト プラクティスについては、プロファイルに基づく最適化ユーザー ガイドを参照してください。
ソース コード アドレス: pgo 最適化ソース コード。
推奨読書
オープンソースアドレス
記事とサンプル コードは、GitHub のオープン ソースです: Go 言語の初心者、中級者、上級のチュートリアル。
公式アカウント: 高度なコーディング。公式アカウントをフォローして、最新の Go 面接の質問とテクノロジースタックを入手してください。
個人ウェブサイト: Jincheng のブログ。
志胡:無忌。
福祉
プログラミング言語の入門から高度な知識 (Go、C++、Python)、バックエンド開発技術スタック、面接の質問などを含む、バックエンド開発学習教材のギフト パックをまとめました。
パブリックアカウント「coding Advanced」をフォローし、バックエンドにメッセージを送信してデータパッケージを受け取ります。このデータは随時更新され、価値があると思われるデータが追加されます。「グループに参加」メッセージを送信して、仲間とコミュニケーションをとって学習したり、質問に答えたりすることもできます。
参考文献
- https://go.dev/blog/pgo-preview