はじめに
この投稿では、ゴルーチンのパフォーマンスについて見ていきます。 ゴルーチンは、一種の非常に安価で軽量なストリームです。 おそらく、それらはErlangのプロセスに似ています。
ドキュメントによると、プログラムでは数十万のゴルーチンを使用できます。 そして、この記事の目的はこれを検証し具体化することです。
記憶
goroutineに割り当てられたメモリのサイズは文書化されていません(数キロバイトと言われています)が、さまざまなマシンでのテストとインターネットでの多くの確認により、この数値を4〜4.5キロバイトに絞り込むことができます 。 つまり、5 GBで100万回のゴルーチンに十分なマージンがあります。
性能
コードをgoroutineに割り当てると、どれだけのプロセッサ時間が失われるかを判断する必要があります。 そのためには、関数を呼び出す前にgoキーワードを入力するだけでよいことを思い出してください。
go testFunc()
ゴルーチンは、主にマルチタスクを達成する手段です。 デフォルトでは、GOMAXPROCS変数がシステムで設定されていない場合、プログラムは1つのスレッドのみを使用します。 すべてのプロセッサコアを使用するには、それらに番号を書き込む必要があります。GOMAXPROCS= 2をエクスポートします。 変数は実行時に読み取られるため、変更のたびにプログラムを再コンパイルする必要はありません。
ゴルーチンの作成、それらの切り替え、時には別のスレッドへの移動、異なるスレッドのゴルーチン間のメッセージの転送に時間がかかることがわかりました。 後者を回避するために、1つのスレッドのみでテストを開始します。
すべてのアクションは、次を使用してネットトップで実行されます。
- Atom D525デュアルコア1.8 GHz
- 4Gb DDR3
- Go r60.3
- Arch Linux x86_64
方法論
調査した関数のジェネレータは次のとおりです。
func genTest(n int)func(res chan <-interface {}){ return func(res chan <-interface {}){ for i:= 0; i <n; i ++ { math.Sqrt(13) } res <-true } }
そして、それぞれ13、1、10、100、1000、5000回からルートを減算した取得関数のセットがあります。
testFuncs := [] func (chan <- interface {}) { genTest(1), genTest(10), genTest(100), genTest(1000), genTest(5000) }
ここで、各関数をループでX回実行し、次にX goroutinesで実行します。 そして、費やした時間を比較します。 また、ガベージコレクションについても忘れないでください。 結果への影響を最小限に抑えるために、すべてのゴルーチンが解決した後に明示的に呼び出してから、操作の終了をマークします。
もちろん、正確性のために、各テストは何度も実行されます。 プログラムの合計実行時間は約16時間かかりました。
シングルスレッド
export GOMAXPROCS= 1
グラフからわかるように、実行時間がrootの計算にほぼ等しい関数は、ゴルーチンに割り当てられた場合、約4倍の時間がかかります。
残りの4つの機能を詳細に検討してください。
70万個のゴルーチンを同時に使用しても、生産性は80%以上低下しないことがわかります。 最もクールなことは、関数がsqrt(13)の計算とほぼ同じ1000回実行される場合でも、オーバーヘッドが2%に過ぎないことです。 そして5,000回-わずか1%! そして、これらの値は、働くゴルーチンの数に実質的に依存していないようです! つまり、唯一の制限はメモリです。
結論:
10個のルートの計算よりも多くの独立したコード(タイムアウトを含む)が実行され、それを並行して実行する場合は、ゴルーチンで大胆に強調表示します。 これらのサイトを10個または100個も簡単にまとめることができれば、生産性の損失はそれぞれわずか20%または2%になります。
複数のスレッド
次に、複数のプロセッサコアを一度に使用したい状況を考えてみましょう。 私の場合、そのうちの2つしかありません。
export GOMAXPROCS= 2
次に、テストプログラムを再度実行します。
ここでは、コアの数が2倍になったにもかかわらず、最初の2つの機能の動作時間が逆に悪化したことがはっきりとわかります。 みましょう。 これは、それらを別のスレッドに転送するコストが実行よりも大きいためです:)
これまでのところ、プランナーはそのような状況を解決することはできませんが、Goの著者は将来そのような欠陥を修正することを約束します。
そしてここでは、最後の2つの関数が両方のコアをほぼ最大限に使用していると考えることができます。 私のネットトップでは、個々の機能はそれぞれ〜45μsおよび〜230μs実行されます。
おわりに
言語の若さと、スケジューラの一時的な不十分な実装にもかかわらず、パフォーマンスは非常に楽しいです。 特に使いやすさと組み合わせて。
ヒントとして、ゴルーチンとして1マイクロ秒未満で機能する関数を使用しないようにすることをお勧めします。 そして、1ミリ秒以上の作業を自由に使用してください:)
PS他の言語、たとえばerlangで同様のテストを見るといいでしょう。 ウィキペディアは、最大2,000万個のプロセスを実行する試みが成功したと報告しています!