基本的なseqlockの単純化されたバージョンを使用して 、gcc はload(memory_order_seq_cst)
でコードをコンパイルするときに、非アトミック ロードをアトミックに並べ替えます-O3
。この並べ替えは、他の最適化レベルでコンパイルする場合や、clang でコンパイルする場合 ( on であってもO3
) は観察されません。この並べ替えは、確立する必要がある同期との関係に違反しているようです.gccがこの特定のロードを並べ替える理由と、これが標準で許可されているかどうかを知りたいです.
次のload
関数を検討してください。
auto load()
{
std::size_t copy;
std::size_t seq0 = 0, seq1 = 0;
do
{
seq0 = seq_.load();
copy = value;
seq1 = seq_.load();
} while( seq0 & 1 || seq0 != seq1);
std::cout << "Observed: " << seq0 << '\n';
return copy;
}
seq_
seqlock 手順に従って、このリーダーは、 であると定義されているの 2 つのインスタンスをロードできるようになるまでスピンしますstd::atomic<std::size_t>
。value
の 2 つの負荷の間seq_
)。さらに、これらのロードは (デフォルトの引数として) でタグ付けされているため、最初のロードを超えて上に並べ替えたり、後者の下に並べ替えたりすることはできないためmemory_order_seq_cst
、命令は反復ごとに実行されると思います。copy = value;
ただし、生成されたアセンブリvalue
は、最初のロードの前からロードを発行しseq_
、ループの外でも実行されます。value
これにより、不適切な同期や、seqlock アルゴリズムによって解決されない破損した読み取りが発生する可能性があります。sizeof(value)
さらに、これは が 123 バイト未満の場合にのみ発生することに気付きました 。何らかのタイプ >= 123 バイトになるように変更value
すると、正しいアセンブリが生成され、 の 2 つのロードの間の各ループ反復でロードされますseq_
。この一見恣意的なしきい値が、生成されるアセンブリを決定する理由はありますか?
このテスト ハーネス は、私の Xeon E3-1505M での動作を明らかにします。リーダーから "Observed: 2" が出力され、値 65535 が返されます。の観察された値seq_
と から返された負荷のこの組み合わせは、 でパブリッシュするライター スレッドとで読み取るリーダー スレッドvalue
によって確立されるべき同期-関係に違反しているようです。seq.store(2)
memory_order_release
seq_
memory_order_seq_cst
sizeof(value)
gcc がロードを並べ替えるのは有効ですか? もしそうなら、 123 未満の場合にのみそうするのはなぜですか? 最適化レベルに関係sizeof(value)
なく、ロードの順序を変更しません。Clang の codegen は適切で正しいアプローチだと思います。