28

この質問に答える際に、私が確信していなかったOPの状況についてのさらなる質問が出てきました。それは主にプロセッサアーキテクチャの質問ですが、C++11メモリモデルについてのノックオンの質問もあります。

基本的に、OPのコードは、次のコードのために、より高い最適化レベルで無限にループしていました(簡単にするために少し変更されています)。

while (true) {
    uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
    if (ov & MASK) {
        continue;
    }
    if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
        break;
    }
}

__sync_val_compare_and_swap()GCCのアトミックCASはどこに組み込まれていますか。GCCは、ループに入る前にbits_ & mask検出された場合true、CAS操作を完全にスキップして、これを(合理的に)無限ループに最適化したので、次の変更を提案しました(これは機能します)。

while (true) {
    uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
    if (ov & MASK) {
        __sync_synchronize();
        continue;
    }
    if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
        break;
    }
}

私が答えた後、OPはに変更bits_することvolatile uint8_tもうまくいくようだと述べました。volatile通常は同期に使用されるべきではないので、そのルートに進まないことを提案しました。とにかく、ここでフェンスを使用することにはそれほどマイナス面はないようです。

しかし、私はそれについてもっと考えました、そしてこの場合、それがov & MASK無期限に古くなった値に基づいていない限り(すなわち、更新の実際の試行bits_が同期されるため、ループは最終的に壊れます)。したがって、既存のプロセッサのように、別のスレッドによって更新されたvolatile場合に、このループが最終的に終了することを保証するには、ここで十分ですか?言い換えると、明示的なメモリフェンスがない場合、コンパイラによって最適化されていない読み取りが、代わりにプロセッサによって無期限に効果的に最適化されることは実際に可能ですか?(編集:bits_bits_ & MASK == false明確にするために、ここでは、読み取りがコンパイラーによってループで出力されるという仮定を前提として、最新のハードウェアが実際に何を行う可能性があるかを尋ねています。したがって、C ++セマンティクスで表現するのは便利ですが、技術的には言語の問題ではありません。)

これはハードウェアの角度ですが、少し更新して、C ++ 11メモリモデルについても回答可能な質問にするために、上記のコードの次のバリエーションを検討してください。

// bits_ is "std::atomic<unsigned char>"
unsigned char ov = bits_.load(std::memory_order_relaxed);
while (true) {
    if (ov & MASK) {
        ov = bits_.load(std::memory_order_relaxed);
        continue;
    }
    // compare_exchange_weak also updates ov if the exchange fails
    if (bits_.compare_exchange_weak(ov, ov | MASK, std::memory_order_acq_rel)) {
        break;
    }
}

cppreferenceは、std::memory_order_relaxed「アトミック変数周辺のメモリアクセスの並べ替えに制約がない」ことを意味し、実際のハードウェアが実行するかどうかに関係なく、準拠する実装で別のスレッドで更新された後、更新された値をbits_.load(std::memory_order_relaxed)技術的に読み取ることbits_ができないことを意味します。 ?

編集:私はこれを標準(29.4 p13)で見つけました:

実装では、妥当な時間内にアトミックストアをアトミックロードから見えるようにする必要があります。

したがって、更新された値を「無限に長く」待つことは(ほとんど?)問題外ですが、それ以外の特定の時間間隔の鮮度が「合理的」であるという確固たる保証はありません。それでも、実際のハードウェアの動作については疑問が残ります。

4

4 に答える 4

9

C ++ 11アトミックは、次の3つの問題を処理します。

  1. スレッドスイッチなしで完全な値の読み取りまたは書き込みを確実に行う。これにより、裂けを防ぎます。

  2. コンパイラがアトミックな読み取りまたは書き込みを介してスレッド内の命令を並べ替えないようにします。これにより、スレッド内での順序付けが保証されます。

  3. アトミック書き込みの前にスレッド内に書き込まれたデータが、アトミック変数を読み取り、書き込まれた値を参照するスレッドによって認識されることを(メモリ順序パラメーターの適切な選択のために)保証します。これが可視性です。

使用するmemory_order_relaxed場合、リラックスした店舗や荷物からの視認性は保証されません。最初の2つの保証があります。

実装は、順序が緩和されている場合でも、妥当な時間内にメモリ書き込みを表示できるようにする必要があります(つまり、推奨されます)。それは言うことができる最高のことです。遅かれ早かれ、これらのものが現れるはずです。

したがって、正式には、リラックスした書き込みをリラックスした読み取りで表示することのない実装は、言語定義に準拠しています。実際には、これは起こりません。

何をするかについてvolatileは、コンパイラのベンダーに問い合わせてください。それは実装次第です。

于 2013-03-17T23:05:42.877 に答える
4

std::memory_order_relaxed負荷が負荷の新しい値を決して返さないことは技術的に合法です。実装がこれを行うかどうかについては、私にはわかりません。

参照:http ://www.developerfusion.com/article/138018/memory-ordering-for-atomic-operations-in-c0x/ "唯一の要件は、同じスレッドから単一のアトミック変数にアクセスできないことです。並べ替え:特定のスレッドがアトミック変数の特定の値を確認すると、そのスレッドによる後続の読み取りでは、変数の以前の値を取得できなくなります。」

于 2013-03-17T23:11:47.113 に答える
4

プロセッサにキャッシュコヒーレンスプロトコルがない場合、または非常に単純な場合は、キャッシュから古いデータをフェッチする負荷を「最適化」できます。現在、ほとんどの最新のマルチコアCPUは、キャッシュコヒーレンシプロトコルを実装しています。ただし、A9より前のARMにはありませんでした。非CPUアーキテクチャもキャッシュコヒーレンシを持たない可能性があります(ただし、おそらくC ++メモリモデルに準拠していません)。

その他の問題は、多くのアーキテクチャ(ARMおよびx86を含む)でメモリアクセスの並べ替えが許可されていることです。プロセッサが同じアドレスへの繰り返しアクセスに気付くほど賢いかどうかはわかりませんが、疑わしいです(コンパイラが気付くことができるはずなので、まれにスペースと時間がかかりますが、後でアクセスする可能性が高いため、わずかなメリットがありますL1ヒットである)が、技術的には分岐が行われると推測でき、最初のアクセスの前に2番目のアクセスを並べ替えることができます(可能性は低いですが、IntelとARMのマニュアルを正しく読んだ場合は許可されます)。

最後に、キャッシュコヒーレンシに準拠していない外部デバイスがあります。CPUがメモリマップドIO/DMAで通信する場合、ページはキャッシュ不可としてマークする必要があります(そうでない場合、L1 / L2 / L3 / ...キャッシュではデータが停止します)。このような場合、プロセッサは通常、読み取りと書き込みを並べ替えません(詳細については、プロセッサのマニュアルを参照してください。よりきめ細かい制御が可能です)。コンパイラは、を使用する必要がありますvolatile。ただし、アトミックは通常キャッシュベースであるため、アトミックは必要ないか、使用できます。

このような強力なキャッシュコヒーレンシが将来のプロセッサで利用可能になるかどうかについては、お答えできません。仕様に厳密に従うことをお勧めします(「ポインターをintに格納することの何が問題になっていますか?確かに、4GiBを超えるユーザーはいないので、32bアドレスは十分に大きいです。」)。正しさは他の人から答えられたので、私はそれを含めません。

于 2013-03-18T00:53:25.097 に答える
1

これが私の見解ですが、私はこのトピックについてあまり知識がないので、一粒の塩でそれを取ります。

キーワード効果はvolatileコンパイラに依存する可能性がありますが、実際には直感的に期待できることを実行すると想定します。つまり、ユーザーがデバッガの変数の値を検査できないようなエイリアシングやその他の最適化を回避します。その変数の存続期間中の実行。これは、揮発性の意味についての答えにかなり近い(そしておそらく同じ) 。

直接的な意味は、変数にアクセスするコードブロックは、volatile変数vを変更するとすぐにそれをメモリにコミットする必要があるということです。フェンスは、他の更新と順番に発生するようにしますが、いずれにしても、ソースレベルで変更されたv場合、アセンブリ出力にストアがあります。v

確かに、あなたが尋ねる質問は、vレジスタにロードされた場合、何らかの計算によって変更されていない場合、CPUvが以前に取得した値を単に再利用するのではなく、任意のレジスタへの読み取りを再度実行するように強制することです。

答えは、CPUはメモリセルが最後の読み取りから変更されていないと想定できないということだと思います。シングルコアシステムであっても、メモリアクセスはCPUに厳密に予約されていません。他の多くのサブシステムは、読み取り/書き込みでアクセスできます(これがDMAの背後にある原則です)。

CPUがおそらく実行できる最も安全な最適化は、値がキャッシュ内で変更されたかどうかを確認し、それをvメモリ内の状態のヒントとして使用することです。キャッシュは同期を保つ必要があります。DMAに接続されたキャッシュ無効化メカニズムのおかげでメモリを使用できます。その条件では、問題はマルチコアでのキャッシュコヒーレンシに戻り、マルチスレッドの状況では「書き込み後に書き込み」を行います。volatileすでにご存知のように、これらの変更操作はアトミックではないため、この最後の問題は単純な変数では効果的に処理できません。

于 2013-03-18T00:41:24.693 に答える