web-dev-qa-db-ja.com

C ++コンパイラは、異なるが隣接するメモリロケーションが異なるスレッドで安全に使用されることを保証するために何をしますか?

構造体があるとしましょう:

struct Foo {
  char a;  // read and written to by thread 1 only
  char b;  // read and written to by thread 2 only
};

私が理解したことから、C++標準は、2つのスレッドが2つの異なるメモリ位置で動作する場合、上記の安全性を保証します。

ただし、char aとchar bは同じキャッシュライン内にあるため、コンパイラは追加の同期を行う必要があると思います。

ここで正確に何が起こりますか?

43
Nathan Doromal

これはハードウェアに依存します。私がよく知っているハードウェアでは、C++は特別なことをする必要はありません。ハードウェアの観点から、キャッシュされた行でも異なるバイトにアクセスするのは「透過的に」処理されるからです。ハードウェアから見ると、この状況は実際にはそうではありません

char a[2];
// or
char a, b;

上記の例では、独立してアクセスできることが保証されている2つの隣接するオブジェクトについて説明しています。

しかし、私は理由のために「透明に」引用符で囲みました。本当にそのようなケースがある場合、「パフォーマンスの面で」「偽共有」に苦しむ可能性があります-これは、2つ(またはそれ以上)のスレッドが隣接するメモリに同時にアクセスし、複数のCPUのキャッシュにキャッシュされることで発生します。これにより、キャッシュが常に無効になります。現実には、これが可能な場合に発生しないように注意する必要があります。

36
SergeyA

他の人が説明したように、一般的なハードウェアには特に何もありません。ただし、問題があります:コンパイラは、他のスレッドが問題のメモリ位置にアクセスしていないことを証明できない限り、特定の最適化の実行を控える必要があります。

std::array<std::uint8_t, 8u> c;

void f()
{
    c[0] ^= 0xfa;
    c[3] ^= 0x10;
    c[6] ^= 0x8b;
    c[7] ^= 0x92;
}

ここで、シングルスレッドメモリモデルでは、コンパイラは次のようなコードを出力できます(擬似アセンブリ。リトルエンディアンハードウェアを想定しています)。

load r0, *(std::uint64_t *) &c[0]
xor r0, 0x928b0000100000fa
store r0, *(std::uint64_t *) &c[0]

これは、個々のバイトをxorするよりも一般的なハードウェアで高速になる可能性があります。ただし、インデックス1、2、4、5のcの影響を受けない(および言及されていない)要素の読み取りと書き込みを行います。他のスレッドがこれらのメモリ位置に同時に書き込みを行うと、これらの変更が上書きされる可能性があります。

このため、これらのような最適化は、マルチスレッドメモリモデルでは使用できないことがよくあります。コンパイラが一致する長さのロードとストアのみを実行するか、ギャップがない場合にのみアクセスをマージする限り(たとえば、c[6]c[7]へのアクセスはまだマージできます)、ハードウェアは通常、正しい実行に必要な保証。

(つまり、DEC Alphaは他のアーキテクチャのようにデータ依存性としてポインタを追跡しないため、メモリ順序の保証が弱く直感に反するアーキテクチャがいくつかあります/あるので、いくつかのアーキテクチャに明示的なメモリバリアを導入する必要があります低レベルのコードの場合。ややよく知られている この問題に関するLinus Torvaldsの小言 がありますが、準拠するC++実装そのような問題からあなたを保護すると期待されます。)

21
Arne Vogel