Go との個人プロジェクトで、Bovespa から金融資産に関する情報を取得します。
このシステムはゴルーチンによる同時実行性と並列性を最大限に活用し、8 秒ごとに資産情報 (ビジネス計算とともに) を更新します。
当初、エラーや警告は表示されませんでしたが、一部のゴルーチンの実行に他のゴルーチンよりも時間がかかっていることに気付きました。
より具体的には、p99 時間は 0.03 ms でしたが、ある時点では 0.9 ms まで増加しました。これにより、問題をさらに調査することになりました。
GOMAXPROCS 変数に基づいて作成されたセマフォ goroutine プールを使用していることがわかりました。
しかし、このアプローチには問題があることに気づきました。
GOMAXPROCS 変数を使用すると、コンテナーで使用可能なコアの数が正しく取得されません。コンテナーの使用可能なコアが VM の合計よりも少ない場合、VM の合計が考慮されます。たとえば、私の VM には利用可能なコアが 8 個ありますが、コンテナーには 4 個しかありませんでした。その結果、同時に実行する 8 つのゴルーチンが作成され、スロットリングが発生しました。
一晩かけて多くの調査を行った結果、コンテナ内にあるかどうかに関係なく、GOMAXPROCS 変数をより効率的に自動的に調整する Uber によって開発されたライブラリを発見しました。このソリューションは非常に安定していて効率的であることが証明されました: automaxprocs
Linux コンテナーの CPU クォータと一致するように GOMAXPROCS を自動的に設定します。
go get -u go.uber.org/automaxprocs
import _ "go.uber.org/automaxprocs"
func main() {
// Your application logic here.
}
Uber の内部ロード バランサーから測定されたデータ。 200% の CPU クォータ (つまり 2 コア) でロード バランサーを実行しました:
GOMAXPROCS | RPS | P50 (ミリ秒) | P99.9 (ミリ秒) |
---|---|---|---|
1 | 28,893.18 | 1.46 | 19.70 |
2 (クォータと等しい) | 44,715.07 | 0.84 | 26.38 |
3 | 44,212.93 | 0.66 | 30.07 |
4 | 41,071.15 | 0.57 | 42.94 |
8 | 33,111.69 | 0.43 | 64.32 |
デフォルト (24) | 22,191.40 | 0.45 | 76.19 |
GOMAXPROCS が CPU クォータを超えて増加すると、P50 はわずかに減少しますが、P99 まで大幅に増加します。また、処理される合計 RPS も減少していることがわかります。
GOMAXPROCS が割り当てられた CPU クォータよりも高い場合、大幅なスロットルも発生しました:
$ cat /sys/fs/cgroup/cpu,cpuacct/system.slice/[...]/cpu.stat nr_periods 42227334 nr_throttled 131923 throttled_time 88613212216618
GOMAXPROCS が CPU クォータに一致するように削減されると、CPU スロットルは発生しなくなりました。
このライブラリの使用を実装した後、問題は解決され、p99 時間は常に 0.02 ms のままになりました。この経験により、同時システムにおける可観測性とプロファイリングの重要性が強調されました。
以下は非常に単純な例ですが、パフォーマンスの違いを示しています。
Go のネイティブ テストと benckmak パッケージを使用して、2 つのファイルを作成しました:
benchmarking_with_enhancement_test.go:
package main import ( _ "go.uber.org/automaxprocs" "runtime" "sync" "testing" ) // BenchmarkWithEnhancement Função com melhoria, para adicionar o indice do loop em um array de inteiro func BenchmarkWithEnhancement(b *testing.B) { // Obtém o número de CPUs disponíveis numCPUs := runtime.NumCPU() // Define o máximo de CPUs para serem usadas pelo programa maxGoroutines := runtime.GOMAXPROCS(numCPUs) // Criação do semáforo semaphore := make(chan struct{}, maxGoroutines) var ( // Espera para grupo de goroutines finalizar wg sync.WaitGroup // Propriade mu sync.Mutex // Lista para armazenar inteiros list []int ) // Loop com mihão de indices for i := 0; ibenchmarking_without_enhancement_test.go:
package main import ( "runtime" "sync" "testing" ) // BenchmarkWithoutEnhancement Função sem a melhoria, para adicionar o indice do loop em um array de inteiro func BenchmarkWithoutEnhancement(b *testing.B) { // Obtém o número de CPUs disponíveis numCPUs := runtime.NumCPU() // Define o máximo de CPUs para serem usadas pelo programa maxGoroutines := runtime.GOMAXPROCS(numCPUs) // Criação do semáforo semaphore := make(chan struct{}, maxGoroutines) var ( // Espera para grupo de goroutines finalizar wg sync.WaitGroup // Propriade mu sync.Mutex // Lista para armazenar inteiros list []int ) // Loop com mihão de indices for i := 0; iこれらの違いは、Uber ライブラリのインポートを使用することです。
2 つの CPU が使用されると想定してベンチマークを実行した場合、結果は次のようになりました。
ns/op: 特定の操作の実行にかかる時間の平均をナノ秒単位で提供します。
使用可能な CPU の合計は 8 コアであり、これが runtime.NumCPU() プロパティによって返されたものであることに注意してください。ただし、ベンチマークの実行時と同様に、使用する CPU は 2 つだけであると定義し、automaxprocs を使用しないファイルでは、一度に実行できるゴルーチンの制限が 8 つであり、最も効率的なのは 2 つであると定義しました。この方法では、割り当てを少なくすることで実行がより効率的になります。
したがって、アプリケーションの可観測性とプロファイリングの重要性は明らかです。
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3