私はOpenMPのスケジューリング、具体的にはさまざまなタイプを勉強しています。各タイプの一般的な動作は理解していますが、dynamic
とguided
のどちらのスケジューリングを選択するかについて明確にすることは役立ちます。
Intelのドキュメント describe dynamic
スケジューリング:
内部作業キューを使用して、各スレッドにループ反復のチャンクサイズのブロックを与えます。スレッドが終了すると、ワークキューの先頭からループ反復の次のブロックを取得します。デフォルトでは、チャンクサイズは1です。このスケジューリングタイプを使用する場合は、余分なオーバーヘッドが発生するため注意してください。
guided
スケジューリングについても説明します。
動的スケジューリングに似ていますが、チャンクサイズは大きくなり始め、反復間の負荷の不均衡をより適切に処理するために小さくなります。オプションのチャンクパラメータは、使用する最小サイズのチャンクを指定します。デフォルトでは、チャンクサイズはおよそloop_count/number_of_threadsです。
guided
スケジューリングは実行時に動的にチャンクサイズを縮小するため、なぜdynamic
スケジューリングを使用するのでしょうか?
私はこの質問を調査しました ダートマスからこの表を見つけました :
guided
はhigh
オーバーヘッドを持つものとしてリストされますが、dynamic
はオーバーヘッドが中程度です。
これは最初は理にかなっていましたが、さらに調査すると、このトピックに関する Intelの記事を読む になりました。前の表から、guided
スケジューリングは実行時のチャンクサイズの分析と調整のために(正しく使用された場合でも)時間がかかると理論付けました。ただし、Intelの記事には次のように記載されています。
ガイド付きスケジュールは、小さなチャンクサイズを制限として最適に機能します。これにより、最大限の柔軟性が得られます。大きなチャンクサイズで悪化する理由は明らかではありませんが、大きなチャンクサイズに制限すると時間がかかりすぎることがあります。
チャンクサイズがguided
に関係するのはなぜdynamic
よりも時間がかかるのですか? 「柔軟性」の欠如は、チャンクサイズをロックしすぎることでパフォーマンスの低下を引き起こすことには意味があります。しかし、私はこれを「オーバーヘッド」とは言わず、ロックの問題は以前の理論を信用しません。
最後に、それは記事に記載されています:
動的なスケジュールは最も柔軟性がありますが、スケジュールが間違っているとパフォーマンスが最大になります。
dynamic
スケジューリングがstatic
よりも最適であるのは理にかなっていますが、なぜguided
よりも最適なのでしょうか?それは私が疑問にしているオーバーヘッドですか?
この やや関連するSO post は、スケジューリングタイプに関連するNUMAを説明します。必要な組織は「先着順」で失われるため、この質問とは無関係です。これらのスケジューリングタイプの動作。
dynamic
スケジューリングは合体してパフォーマンスの向上を引き起こす可能性がありますが、guided
にも同じ仮説が適用されるはずです。
参考までに、Intelの記事からのさまざまなチャンクサイズにわたる各スケジューリングタイプのタイミングを以下に示します。それは1つのプログラムからの記録のみで、一部のルールはプログラムとマシンごとに異なる方法で適用されます(特にスケジューリングの場合)が、一般的な傾向を提供する必要があります。
[〜#〜] edit [〜#〜](私の質問の中核):
guided
スケジューリングの実行時間に影響を与えるものは何ですか?具体例?場合によってはdynamic
より遅いのはなぜですか?guided
よりもdynamic
を好むでしょうか、その逆ですか?ガイド付きスケジューリングの実行時間に影響するものは何ですか?
考慮すべき3つの効果があります。
動的/ガイド付きスケジューリングのポイントは、各ループ反復に同じ量の作業が含まれない場合に作業の分散を改善することです。基本的に:
schedule(dynamic, 1)
は、最適な負荷分散を提供しますdynamic, k
_は常に_guided, k
_よりもと同等かそれ以上の負荷分散を行います標準では、各チャンクのサイズはproportionalに割り当てられていない反復の数をチーム内のスレッドの数で割ってk
に減少することを義務付けています。
GCC OpenMP実装 は、proportionalを無視して、文字通りこれを受け取ります。たとえば、4つのスレッド_k=1
_の場合、_8, 6, 5, 4, 3, 2, 1, 1, 1, 1
_として32回反復されます。今私見これは本当に愚かです:最初の1/nの反復に1/nを超える作業が含まれていると、負荷のバランスが悪くなります。
具体例?なぜ場合によっては動的より遅いのですか?
さて、ループの繰り返しで内部の仕事が減少する些細な例を見てみましょう:
_#include <omp.h>
void work(long ww) {
volatile long sum = 0;
for (long w = 0; w < ww; w++) sum += w;
}
int main() {
const long max = 32, factor = 10000000l;
#pragma omp parallel for schedule(guided, 1)
for (int i = 0; i < max; i++) {
work((max - i) * factor);
}
}
_
実行は次のようになります1:
ご覧のとおり、guided
はここで非常に悪い結果をもたらします。 guided
は、さまざまな種類の作業配分に対してはるかに優れています。ガイド付きを異なる方法で実装することもできます。 clang(IIRCはIntelに由来)の実装は、 はるかに洗練された です。私は、GCCの単純な実装の背後にある考えを本当に理解していません。私の目には、最初のスレッドに_1/n
_の作業を与えると、動的な負荷のブランキングの目的を事実上無効にします。
現在、各動的チャンクは、共有状態にアクセスするため、パフォーマンスにある程度の影響があります。 guided
のオーバーヘッドは、少し多くの計算があるため、dynamic
よりもチャンクごとにわずかに高くなります。ただし、_guided, k
_の合計ダイナミックチャンクは_dynamic, k
_よりも少なくなります。
オーバーヘッドも実装に依存します。共有状態を保護するためにアトミックまたはロックを使用するかどうか。
ループの繰り返しで整数のベクトルに書き込むとしましょう。 1つおきの反復が異なるスレッドによって実行される場合、ベクトルの2つおきの要素は異なるコアによって書き込まれます。そうすることで、隣接する要素を含むキャッシュラインを競合させるため、それは本当に悪いことです(偽共有)。チャンクサイズが小さい場合や、キャッシュにうまく合わないチャンクサイズがある場合は、チャンクの「エッジ」でパフォーマンスが低下します。これが、通常、大きなNice(_2^n
_)チャンクサイズを好む理由です。 guided
は平均して大きなチャンクサイズを提供できますが、_2^n
_(または_k*m
_)は提供できません。
この回答 (すでに参照した)、NUMAの観点から動的/ガイド付きスケジューリングの欠点について詳しく説明していますが、これはローカリティ/キャッシュにも当てはまります。
さまざまな要因と詳細を予測することの難しさを考えると、特定のシステム、特定の構成、特定のコンパイラで特定のアプリケーションを測定することをお勧めします。残念ながら、完璧なパフォーマンスの移植性はありません。個人的には、これはguided
に特に当てはまると主張します。
動的または逆の場合にガイド付きを優先するのはいつですか?
オーバーヘッド/反復ごとの作業について特定の知識がある場合は、_dynamic, k
_が適切なk
を選択することで最も安定した結果をもたらすと言えます。特に、実装がどれほど賢いかにはあまり依存しません。
一方、guided
は、少なくとも賢い実装では、妥当なオーバーヘッド/負荷分散比率を備えた、最初の推測として適切かもしれません。後の反復時間が短いことがわかっている場合は、guided
に特に注意してください。
コンパイラ/ランタイムを完全に制御するschedule(auto)
と、実行時にスケジューリングポリシーを選択できるschedule(runtime)
もあります。
これが説明されると、上記のソースはあなたの説明をサポートしますか?彼らはまったく矛盾していますか?
このanserを含むソースを一粒の塩で取ります。あなたが投稿したチャートも、私のタイムラインの写真も、科学的に正確な数字ではありません。結果には大きなばらつきがあり、エラーバーはありません。これらのデータポイントがほとんどない場所にある可能性があります。また、チャートは、Work
コードを開示せずに、私が言及した複数の効果を組み合わせています。
[Intelドキュメントから]
デフォルトでは、チャンクサイズはおよそloop_count/number_of_threadsです。
これは、iccが私の小さな例をより良く処理するという私の観察と矛盾しています。
1:視覚化にGCC 6.3.1、Score-P/Vampirを使用し、着色のために2つの交互の仕事関数。