web-dev-qa-db-ja.com

{A = a;の場合B = b; }、「A = a」は「B = b」の前に厳密に実行されますか?

ABa、およびbがすべて変数であり、ABのアドレスがあるとします。 、a、およびbはすべて異なります。次に、次のコードの場合:

A = a;
B = b;

CおよびC++標準では、A=aの前にB=bを厳密に実行する必要がありますか? ABa、およびbのアドレスがすべて異なる場合、コンパイラーは2つのステートメントの実行シーケンスを一部のステートメントと交換できます。最適化などの目的は?

私の質問に対する答えがCとC++で異なる場合は、両方を知りたいと思います。

編集:質問の背景は次のとおりです。ボードゲームのAI設計では、最適化のために ロックレス共有ハッシュテーブル を使用します。volatile制限を追加しない場合、その正確性は実行順序に大きく依存します。

51
ACcreator

どちらの標準でも、観察可能な動作が変わらない限り、これらの指示を順不同で実行することが許可されています。これは、as-ifルールとして知られています。

コメントで指摘されているように、「観察可能な振る舞い」が意味するのは、定義された振る舞いを持つプログラムの観察可能な振る舞いであることに注意してください。プログラムの動作が未定義の場合、コンパイラーはそれについて推論することを免除されます。

56
David Heffernan

コンパイラーは、プログラムの観察可能な動作をエミュレートする義務があるだけなので、並べ替えがその原則に違反しない場合は許可されます。動作が明確に定義されていると仮定すると、プログラムにデータ競合などの 未定義の動作 が含まれている場合、プログラムの動作は予測できず、コメントされているように、クリティカルセクションを保護するために何らかの形式の同期を使用する必要があります。

便利なリファレンス

これをカバーする興味深い記事は コンパイル時のメモリオーダリング であり、次のように書かれています。

コンパイラ開発者とCPUベンダーが普遍的に従うメモリリオーダリングの基本的なルールは、次のように表現できます。

シングルスレッドプログラムの動作を変更してはなりません。

この記事では、この並べ替えを確認できる簡単なプログラムを提供しています。

int A, B;  // Note: static storage duration so initialized to zero

void foo()
{
    A = B + 1;
    B = 0;
}

より高い最適化レベルでは、B = 0A = B + 1の前に実行され、 godbolt を使用してこの結果を再現できます。これにより、-O3を使用すると次のようになります(ライブで見る):

movl    $0, B(%rip) #, B
addl    $1, %eax    #, D.1624

どうして?

コンパイラが並べ替えるのはなぜですか?この記事では、アーキテクチャが複雑なため、プロセッサがそうするのとまったく同じ理由であると説明しています。

冒頭で述べたように、コンパイラは、プロセッサが行うのと同じ理由で、メモリの相互作用の順序を変更します。パフォーマンスの最適化です。このような最適化は、最新のCPUの複雑さの直接的な結果です。

規格

ドラフトC++標準では、これはセクション1.9プログラム実行でカバーされています(今後の強調):

この国際規格のセマンティック記述は、パラメータ化された非決定論的抽象マシンを定義します。この国際規格は、適合実装の構造に要件を課していません。特に、抽象マシンの構造をコピーしたりエミュレートしたりする必要はありません。 むしろ、抽象マシンの観察可能な動作を(のみ)エミュレートするには、準拠する実装が必要です以下で説明します。5

脚注5は、これが as-ifルールとしても知られていることを示しています。

この規定は、「as-if」ルールと呼ばれることもあります。これは、実装がこの国際規格の要件を自由に無視できる結果がプログラムの観察可能な動作から判断できる限り、要件が守られているかのように。たとえば、実際の実装では、式の値が使用されておらず、プログラムの観察可能な動作に影響を与える副作用が発生していないと推測できる場合、式の一部を評価する必要はありません。

ドラフトC99およびドラフトC11標準は、セクション5.1.2.3プログラム実行でこれをカバーしていますが、インデックスに移動してと呼ばれていることを確認する必要があります。 C標準のas-ifルール

as-ifルール、5.1.2.3

ロックフリーの考慮事項に関する最新情報

記事 ロックフリープログラミング入門 はこのトピックを十分にカバーしており、ロックレス共有ハッシュテーブル実装に関するOPの懸念については、このセクションはおそらく最適な:

メモリオーダリング

フローチャートが示すように、マルチコア(または任意の 対称型マルチプロセッサ )のロックフリープログラミングを実行し、環境が逐次一貫性を保証しない場合は常に、 メモリの並べ替え)を防ぐ方法を検討する必要があります

今日のアーキテクチャでは、正しいメモリ順序を適用するツールは、一般に3つのカテゴリに分類され、 コンパイラの並べ替えプロセッサの並べ替え の両方を防ぎます。

  • 軽量の同期またはフェンス命令。これについては 今後の投稿 ;で説明します。
  • 私が行った完全なメモリフェンス命令 前に示した ;
  • 取得または解放のセマンティクスを提供するメモリ操作。

取得セマンティクスは、プログラムの順序でそれに続く操作のメモリの並べ替えを防ぎ、解放セマンティクスは、それに先行する操作のメモリの並べ替えを防ぎます。これらのセマンティクスは、一方のスレッドが情報を公開し、もう一方のスレッドがそれを読み取る、生産者/消費者関係がある場合に特に適しています。これについては、今後の投稿でも詳しく説明します。

25
Shafik Yaghmour

命令の依存関係がない場合、最終的な結果に影響がない場合でも、これらは順不同で実行される可能性があります。より高い最適化レベルでコンパイルされたコードをデバッグしているときに、これを観察できます。

3
Mohit Jain

A = aなので;およびB = b;データの依存関係に関して独立しているため、これは問題ではありません。前の命令の出力/結果が次の命令の入力に影響を与える場合は、順序が重要です。それ以外の場合はそうではありません。これは通常、厳密に順次実行されます。

1

私の読みでは、これはC++標準で機能するために必要です。ただし、これをマルチスレッド制御に使用しようとしている場合、レジスタが正しい順序でメモリに書き込まれることを保証するものがここにはないため、そのコンテキストでは機能しません。

あなたの編集が示すように、あなたはそれが機能しない場所でそれを正確に使用しようとしています。

1
Joshua

これを行うと、次のことが興味深い場合があります。

{ A=a, B=b; /*etc*/ }

セミコロンの代わりにコンマに注意してください。

次に、コンマ演算子のオペランドは常に左から右に評価されるため、C++仕様と確認コンパイラは実行順序を保証する必要があります。これは実際、オプティマイザーが並べ替えによってスレッド同期を破壊するのを防ぐために使用できます。カンマは事実上、並べ替えが許可されない障壁になります。

0