59

私は新しいC++ 11メモリモデルについて読んでいて、std::kill_dependency関数に出くわしました(§29.3/14-15)。なぜそれを使いたいのか理解に苦しむ。

N2664 提案で例を見つけましたが、あまり役に立ちませんでした。

なしでコードを表示することから始めますstd::kill_dependency。ここで、最初の行は依存関係を 2 番目の行に運び、依存関係をインデックス付け操作に運び、次に依存関係をdo_something_with関数に運びます。

r1 = x.load(memory_order_consume);
r2 = r1->index;
do_something_with(a[r2]);

std::kill_dependency2行目と索引付けの間の依存関係を壊すために使用する別の例があります。

r1 = x.load(memory_order_consume);
r2 = r1->index;
do_something_with(a[std::kill_dependency(r2)]);

私が知る限り、これは、インデックス作成と への呼び出しdo_something_withが、2 行目の前に順序付けられた依存関係ではないことを意味します。N2664によると:

これにより、コンパイラはdo_something_with、たとえば、 の値を予測する投機的最適化を実行することにより、への呼び出しを並べ替えることができますa[r2]

値を呼び出すにdo_something_withは、値a[r2]が必要です。仮に、配列がゼロで埋められていることをコンパイラが「知っている」場合、その呼び出しを最適化do_something_with(0);し、他の 2 つの命令に対してこの呼び出しを必要に応じて並べ替えることができます。次のいずれかを生成できます。

// 1
r1 = x.load(memory_order_consume);
r2 = r1->index;
do_something_with(0);
// 2
r1 = x.load(memory_order_consume);
do_something_with(0);
r2 = r1->index;
// 3
do_something_with(0);
r1 = x.load(memory_order_consume);
r2 = r1->index;

私の理解は正しいですか?

他の方法で別のスレッドと同期する場合、呼び出しとこの別のスレッドdo_something_withの順序に関して、これはどういう意味ですか?x.load

私の理解が正しいと仮定すると、私を悩ませることがまだ 1 つあります。コードを書いているとき、どのような理由で依存関係を破棄することを選択するのでしょうか?

4

3 に答える 3

39

memory_order_consume の目的は、コンパイラが、ロックレス アルゴリズムを壊す可能性のある不運な最適化を行わないようにすることです。たとえば、次のコードを検討してください。

int t;
volatile int a, b;

t = *x;
a = t;
b = t;

準拠するコンパイラは、これを次のように変換できます。

a = *x;
b = *x;

したがって、a は b と等しくない場合があります。また、次のこともできます。

t2 = *x;
// use t2 somewhere
// later
t = *x;
a = t2;
b = t;

を使用することによりload(memory_order_consume)、ロードされる値の使用が使用ポイントの前に移動されないようにする必要があります。言い換えると、

t = x.load(memory_order_consume);
a = t;
b = t;
assert(a == b); // always true

標準ドキュメントでは、構造体の特定のフィールドの順序付けのみに関心がある場合を考慮しています。例は次のとおりです。

r1 = x.load(memory_order_consume);
r2 = r1->index;
do_something_with(a[std::kill_dependency(r2)]);

これは、効果的にこれを行うことが許可されていることをコンパイラに指示します。

predicted_r2 = x->index; // unordered load
r1 = x; // ordered load
r2 = r1->index;
do_something_with(a[predicted_r2]); // may be faster than waiting for r2's value to be available

またはこれでも:

predicted_r2 = x->index; // unordered load
predicted_a  = a[predicted_r2]; // get the CPU loading it early on
r1 = x; // ordered load
r2 = r1->index; // ordered load
do_something_with(predicted_a);

コンパイラがdo_something_withr1 または r2 のロードの結果を変更しないことを認識している場合、それを完全に引き上げることさえできます。

do_something_with(a[x->index]); // completely unordered
r1 = x; // ordered
r2 = r1->index; // ordered

これにより、コンパイラは最適化においてもう少し自由度が高くなります。

于 2011-08-22T19:02:41.087 に答える
11

もう 1 つの回答に加えて、C++ コミュニティの決定的なリーダーの 1 人である Scott Meyers が、memory_order_consume をかなり強く叩いたことを指摘します。彼は基本的に、それが標準には存在しないと信じていると言った. 彼は、memory_order_consume が効果を発揮するケースが 2 つあります。

  • 1024+ コアの共有メモリ マシンをサポートするように設計されたエキゾチックなアーキテクチャ。
  • DECアルファ

はい、繰り返しになりますが、DEC Alpha は、何年も後に非常に特殊化されたマシンで行われるまで、他のどのチップにも見られなかった最適化を使用することで、悪名高い方法を見つけました。

特定の最適化は、これらのプロセッサが実際にそのフィールドのアドレスを取得する前にフィールドを逆参照できるようにすることです (つまり、x の予測値を使用して、x を検索する前に x->y を検索できます)。次に戻り、x が期待値であったかどうかを判断します。成功すると、時間を節約できました。失敗すると、戻って x->y を再度取得する必要があります。

Memory_order_consume は、これらの操作を順番に実行する必要があることをコンパイラ/アーキテクチャに伝えます。ただし、最も便利なケースでは、z が変わらない (x->yz) を実行したくなるでしょう。memory_order_consume は、コンパイラーに xy と z を順番に保つように強制します。kill_dependency(x->y).z は、そのような悪質な並べ替えを再開する可能性があることをコンパイラ/アーキテクチャに伝えます。

開発者の 99.999% は、おそらくこの機能が必要な (またはまったく効果がない) プラットフォームで作業することはありません。

于 2013-09-03T05:51:43.743 に答える