Go Scheduler

翻訳者からの序文:これはかなり最新の翻訳ではありませんが(2013年6月)、かなり無料の翻訳ですが、Goの並列実行ブランチの新しいスケジューラに関するわかりやすい出版物です。 このメモの利点は、非常に簡単であるということです。「指先で」レビューのための新しい計画メカニズムを説明します。 「指で」の説明に満足せず、詳細なプレゼンテーションが必要な場合は、 ワークスティーリングによるマルチスレッド計算のスケジューリング -パフォーマンス分析用の厳密で複雑な数学装置、48の書誌事項を含む29ページのプレゼンテーションをお勧めします。



はじめに



Go 1.1の最大の革新の1つは、Dmitry Vyukovが設計した新しいディスパッチャです。 新しいスケジューラにより、コードを変更せずに並列プログラムのパフォーマンスが劇的に向上しました。



ここに書かれているもののほとんどは、著者の元のテキストですでに説明されています。 このテキストは非常に包括的なものですが、純粋に技術的なものです。



新しいプランナーについて知っておくべきことはすべてこの著者のテキストに含まれていますが、私の説明は説明のためのものであり、理解するためにそれを超えています。



Goルーチンにスケジューラが必要なのはなぜですか?



新しいプランナーを見る前に、なぜそれが必要なのかを理解する必要があります。 オペレーティングシステムがスレッドをスケジュールするときにユーザースペーススケジューラを作成する理由



POSIXスレッドAPIは、UNIXオペレーティングシステムに存在するプロセスモデルの非常に論理的な継続であり、もしそうであれば、スレッドはプロセスが持つ多くのコントロールを取得します。



ストリームには独自のシグナルマスクがあり、アフィニティマスクを使用してプロセッサに割り当て、グループに入力でき、使用するリソースをテストできます。 これらのすべての要素はオブジェクトにオーバーヘッドを追加します。Goルーチンを使用するGoプログラムには単純に必要ないため、プログラムに100,000スレッドがある場合、これらのオーバーヘッドが増加します。



もう1つの問題は、オペレーティングシステムがGoモデルに基づいて意味のある計画を決定できないことです。 たとえば、ガベージコレクターでは、コレクターの起動時にすべてのスレッドを停止し、それらのメモリを同期状態にする必要があります。 これには、スレッドがメモリが適切な状態にあることを確認できるポイントに到達するのを待つことが含まれます。



ランダムなポイントで多くのスレッドを計画している場合、ほとんどのスレッドが一貫した状態に達することを期待せざるを得ない可能性が高くなります。 対照的に、Goスケジューラは、メモリが一貫した状態にあることがわかっている場所でのみ再スケジュールの決定を行うことができます。 これは、ガベージコレクションのために停止すると、プロセッサコアでアクティブに実行されているスレッドのみを期待することを意味します。



俳優



通常、計算をスレッドにカットするための3つのモデルがあります。 1つ目はN:1で、複数のユーザースレッドがオペレーティングシステムの単一のカーネルスレッドで実行されています。 この方法には、コンテキストの切り替えが非常に高速であるという利点がありますが、マルチコアシステムを利用する方法はありません。 2番目の方法は1対1で、各ユーザー実行スレッドはオペレーティングシステムの1つのスレッドと一致します。 すべてのコアが自動的に使用されますが、オペレーティングシステムへの割り込みが必要なため、コンテキストの切り替えが遅くなります。



Goは、M:Nスケジューラを使用して、両方の長所を最大限に活用しようとしています。 さらに、オペレーティングシステムの任意の数のスレッド(N)に対して、任意の数のGoルーチン(M)が計画されています。 これにより、コンテキストを迅速に切り替えると同時に、システム内のすべてのカーネルを使用できるようになります。 このアプローチの主な欠点は、スケジューラに含めることが難しいことです。



計画タスクを実行するために、Goスケジューラは3つの主要なエンティティを使用します。







三角形は、オペレーティングシステムの流れを表しています。 オペレーティングシステムはこのようなスレッドの実行を制御し、標準のPOSIXスレッドとほとんど同じように機能します。 実行可能コードでは、これはMマシンと呼ばれます。



円はGoルーチンを表します。 スタック、コマンドポインター、およびGoルーチンを計画するためのその他の重要な情報(ブロックできるチャネルなど)が含まれています。 実行可能コードでは、これはGとして示されます。



長方形は計画コンテキストを表します。 これは、単一のカーネルスレッドでGo-routineコードを実行するローカライズ版のスケジューラーとして理解できます。 これは、N:1スケジューラーからM:Nスケジューラーへの移行を可能にする重要な部分です。 実行時、コンテキストはプロセッサのPとして示されます。 一般的に、要するにすべてです。







この図は、それぞれがGoルーチンを実行する2つのカーネルスレッド(M)を示し、それぞれにコンテキスト(P)があります。 Go Goルーチンの目的のために、スレッドはコンテキストを保持する必要があります。 コンテキストの数は、開始時にGOMAXPROCS環境変数の値から、またはGOMAXPROCS()ランタイム関数によって設定されます。 通常、この値はプログラムの実行中に変化しません。



コンテキストの数が固定されているという事実は、GoコードのGOMAXPROCSセクションのみが常に実行されることを意味します。 これを使用して、たとえば4コアPC上でGoコードを4スレッドで実行するなど、個々のコンピューターのGoプロセス呼び出しを構成できます。



図面内の灰色のGoルーチンは実行されませんが、計画の準備ができています。 これらは、runqueuesと呼ばれるリストに配置されます。 Goルーチンは、次のGoルーチンがgoステートメントを実行するたびに実行キューの最後に追加されます。 コンテキストが計画ポイントから次のGoルーチンを実行する必要があるたびに、実行ルーチンからGoルーチンをプッシュし、スタックと命令ポインターを設定して、Goルーチンが開始します。



ミューテックスの不一致を防ぐために、各コンテキストには独自のローカル実行キューがあります。 Goスケジューラの以前のバージョンには、mutex保護を備えたグローバル実行キューしかありませんでした。 スレッドは、mutexが解放されるのを待ってブロックされることがよくあります。 32コアマシンを使用する場合、これは非常に残念なことであり、可能な限り最大のパフォーマンスを絞り出そうとしました。



スケジューラは、すべてのコンテキストでGoルーチンが実行されている限り、そのような安定した状態で計画を維持します。 ただし、この動作を変更できるシナリオがいくつかあります。



(システム)コールは誰になりますか?



なぜ一般的なコンテキストが必要なのか疑問に思うかもしれません。 実行キューをスレッドにアタッチしてコンテキストを削除することはできませんか? 本当じゃない。 ポイントは、現在のスレッドが何らかの理由でブロックする必要がある場合に他のスレッドに渡すことができるように、コンテキストがあることです。



ブロックする必要がある場合の例は、システムコールを行う場合です。 スレッドは同時にコードを実行できず、システムコールでブロックできないため、スケジューリングを遅らせる可能性があるため、コンテキストを渡す必要があります。







ここでは、スレッドがコンテキストを放棄しているので、別のスレッドがそれを開始できます。 スケジューラは、すべてのコンテキストを実行するのに十分なスレッドがあることを確認します。 上の図のM1は、システムコールを処理するために特別に作成することも、スレッドプールから取得することもできます。 システムコールを実行するスレッドは、オペレーティングシステムでブロックされていますが、技術的にはまだ実行されているため、システムコールを行うGoルーチンでフリーズされます。



システムコールが戻ると、スレッドはコンテキストを取得して、戻るGoルーチンを開始する必要があります。 アクションの通常モードは、他のスレッドの1つからコンテキストを借用することです。 スレッドが借用できない場合、Goルーチンをグローバル実行キューに入れて、フリースレッドのプールに戻るか、スリープモードになります。



グローバル実行キューは、ローカルキューで終了したときにコンテキストが取得(作業)する実行キューです。 コンテキストは、Go-routingのグローバルキューも定期的にチェックします。 そうしないと、グローバルラインナップのGoルーチンが開始されず、飢ofで死ぬ場合があります。



このシステムコールの処理は、GOMAXPROCSが1に設定されている場合でもGoプログラムが複数のスレッドで実行される理由を説明します。ランタイムはシステムコールにGoルーチンを使用し、スレッドを残します。



借用作業



システムの定常状態を変更できるもう1つの方法は、コンテキストがGo-routineスケジュールの範囲外になった場合です。 これは、コンテキストの実行キュー内の作業量が不均衡な場合に発生する可能性があります。 これは、コンテキストが最終的に実行の順番を使い果たしたときに発生する可能性がありますが、システムで実行する作業がまだあります。 Goコードの実行を継続するために、コンテキストはグローバル実行キューからGoルーターを取得できますが、Goルーターが存在しない場合は、別の場所から取得する必要があります。







これは他のコンテキストのどこかにあります。 コンテキストがなくなると、実行キューの約半分を別のコンテキストから借用しようとします。 これにより、各コンテキストで実行される作業が常に行われ、すべてのスレッドが機能の制限で動作していることが確認されます。



次は?



スケジューラには、cgoスレッド、LockOSThread()関数、ネットワークプルとの統合など、より多くの詳細があります。 これらはこれらのメモの範囲を超えていますが、まだ調査する価値があります。 それらについては後で書きます。 もちろん、Goランタイムライブラリには多くの興味深い構成要素があります。



All Articles