web-dev-qa-db-ja.com

「揮発性」は、マルチコアシステムのポータブルCコードで何かを保証しますか?

bunchofother /を見た後 questionsandtheiranswersCの「揮発性」キーワードが正確に何を意味するかについて、広範な合意はないという印象を受けます。

標準自体でさえ、 それが何を意味するか に同意するのに十分なほど明確ではないようです。

他の問題の中で:

  1. それはあなたのハードウェアとあなたのコンパイラに依存して異なる保証を提供するようです。
  2. コンパイラーの最適化には影響しますが、ハードウェアの最適化には影響しません。そのため、独自のランタイム最適化を行う高度なプロセッサーでは、コンパイラーが /で阻止したい最適化を阻止できるかどうかさえ明確ではありません。 。 (一部のコンパイラーは、一部のシステムで一部のハードウェア最適化を防止するために命令を生成しますが、これは決して標準化されていないようです。)

問題を要約すると、「多くのことを読んだ後」では、「揮発性」が次のようなことを保証しているように見えます。値は、レジスタからだけでなく、少なくともコアのL1キャッシュに対しても読み書きされます。 、読み取り/書き込みがコードに現れるのと同じ順序です。これは役に立たないようです。L1キャッシュとの調整では、同じスレッド内でレジスタへの読み取り/書き込みはすでに十分であるからです。他のスレッドとの調整に関して、それ以上のことを保証します。 L1キャッシュとのみ同期することがいつ重要になるかは想像できません。

USE 1
広く同意されたvolatileの唯一の使用は、特定のメモリ位置が(直接、ハードウェアで)制御するメモリ内のビットのように、I/O関数にハードウェアマップされている古いシステムまたは組み込みシステム用であるようです)キーボードのキーが押されているかどうかを示すライト、またはメモリ内のビット(ハードウェアによってキーに直接接続されているため)。

「use 1」は、ターゲットにマルチコアシステムが含まれるポータブルコードでは発生しないようです。

USE 2
「use 1」とそれほど変わらないのは、割り込みハンドラー(ライトの制御やキーからの情報の保存など)によっていつでも読み書きできるメモリです。しかし、これについてはすでに、システムによっては、割り込みハンドラ が実行される可能性があるという問題があります別のコア /付き 独自のメモリキャッシュ 、および「揮発性」は、すべてのシステムでキャッシュの一貫性を保証しません。

したがって、「use 2」は、「volatile」が提供できる範囲を超えているようです。

USE 3
私が見る他の議論の余地のない使用は、コンパイラが認識しない同じメモリを指している同じメモリを指している異なる変数を介したアクセスの誤最適化を防ぐことです。しかし、人々がそれについて話しているわけではないので、これはおそらく議論の余地がないだけです-私はそれについて1つの言及しか見ませんでした。また、C標準では、「異なる」ポインター(関数への異なる引数など)が同じ項目または近くの項目を指す可能性があることをすでに認識しており、コンパイラーがそのような場合でも機能するコードを生成する必要があることをすでに指定しています。しかし、私はこのトピックを最新(500ページ)の標準ですぐに見つけることができませんでした。

"use 3"はおそらく存在しない

従って私の質問:

「揮発性」は、マルチコアシステムのポータブルCコードで何かを保証しますか?


編集-更新

最新の標準 を閲覧した後、答えは少なくとも very 制限されているようです:
1。この規格では、特定のタイプ「volatile sig_atomic_t」に対する特別な扱いを繰り返し規定しています。ただし、この規格では、マルチスレッドプログラムでシグナル関数を使用すると、未定義の動作が発生することも規定されています。したがって、この使用例は、シングルスレッドプログラムとそのシグナルハンドラーの間の通信に限定されているようです。
2。この規格では、setjmp/longjmpに関連する「揮発性」の明確な意味も規定されています。 (重要なコードの例は、他の questions および answers にあります。)

したがって、より正確な質問は次のようになります。
(1)シングルスレッドプログラムがシグナルハンドラーから情報を受信できるようにすることを除いて、マルチコアシステムのポータブルCコードで「揮発性」を保証します、または(2)setjmpコードがsetjmpとlongjmpの間で変更された変数を参照できるようにしますか?

これははい/いいえの質問です。

「はい」の場合、「揮発性」を省略した場合にバグになるバグのない移植可能なコードの例を示すことができればすばらしいでしょう。 「いいえ」の場合、マルチコアターゲットの場合、コンパイラーはこれら2つの非常に特殊なケース以外では「揮発性」を無視してもかまいません。

12
Matt

問題を要約すると、「多くのことを読んだ後」では、「揮発性」が次のようなことを保証しているように見えます。値は、レジスタからだけでなく、レジスタに対しても読み書きされます。読み取り/書き込みがコードに表示されるのと同じ順序でキャッシュ

いいえ、絶対にありません。そして、MT安全なコードの目的では、volatileはほとんど役に立たなくなります。

もしそうなら、揮発性は、L1キャッシュ内のイベントの順序付けが、協調可能な標準的なCPU(マルチコアまたはマザーボード上のマルチCPU)で実行する必要があるすべてであるため、複数のスレッドで共有される変数に非常に適しています。 C/C++またはJavaマルチスレッドの通常の実装を通常の予想コストで可能にする方法で(つまり、ほとんどのアトミックまたは非コンテンツmutex操作での巨大なコストではありません)。

しかし、volatileは、理論上または実際に、キャッシュ内の保証された順序付け(または「メモリの可視性」)をしないことは提供しません。

(注:以下は、標準ドキュメントの正しい解釈、標準の意図、歴史的実践、およびコンパイラ作成者の期待の深い理解に基づいています。このアプローチは、歴史、実際の実践、および実在の人物の期待と理解に基づいています現実の世界。これは、優れた仕様の記述であることが知られていない文書の単語を解析するよりもはるかに強力で信頼性が高く、何度も改訂されています。)

実際には、volatileは、実行中のプログラムのデバッグ情報を任意のレベルの最適化で使用する機能であるptrace-abilityを保証し、デバッグ情報がこれらに対して意味をなすという事実揮発性オブジェクト:

  • ptrace(ptraceに似たメカニズム)を使用して、揮発性オブジェクトが関係する操作の後にシーケンスポイントに意味のあるブレークポイントを設定できます。実際にこれらのポイントで正確にブレークできます(これができるのは、大規模に展開されたループのように、C/C++ステートメントがさまざまなアセンブリの開始点と終了点にコンパイルされる可能性があるため、多くのブレークポイントを設定します);
  • 実行のスレッドが停止している間は、すべての揮発性オブジェクトの値を読み取ることができます。非揮発性ローカル変数は、非定型表現f.exを持つことができます。シフトされた表現:配列のインデックス付けに使用される変数は、インデックス付けを容易にするために、個々のオブジェクトのサイズで乗算される場合があります。または、(同様に変換された変数のすべての使用が続く限り)配列要素へのポインタで置き換えられる可能性があります(整数でdxをduに変更すると考えてください);
  • また、これらのオブジェクトを変更することもできます(メモリマッピングで許可されている限り、const修飾された静的存続期間を持つ揮発性オブジェクトは、メモリ範囲にマップされた読み取り専用であるため)。

厳密なptraceの解釈よりも少し高い揮発性の保証:揮発性の自動変数がスタックにアドレスを持っていることも保証します。これは、それらがレジスターに割り当てられていないためです。変数がレジスタに割り当てられる方法を説明するデバッグ情報を出力しますが、レジスタの状態の読み取りと変更は、メモリアドレスへのアクセスよりも少し複雑です)。

すべての変数を少なくともシーケンスポイントで揮発性と見なす完全なプログラムデバッグ機能は、コンパイラーの「ゼロ最適化」モードによって提供されることに注意してください。このモードは、算術単純化などのささいな最適化を実行します(通常、すべてのモードでの最適化)。しかし、揮発性は非最適化よりも強力です:x-xは、揮発性オブジェクトxの場合は簡略化できますが、揮発性オブジェクトの場合は簡略化できません。

したがって、volatileは、システムコールのコンパイラによるソースからバイナリ/アセンブリへの変換が再解釈、変更、または最適化されていないなど、現状のままコンパイルされることが保証されているコンパイラによる方法。ライブラリ呼び出しはシステム呼び出しである場合とそうでない場合があることに注意してください。多くの公式システム関数は実際にはライブラリ関数であり、割り込みの薄い層を提供し、通常は最後にカーネルに委ねます。 (特にgetpidはカーネルに移動する必要がなく、情報を含むOSによって提供されるメモリ位置を読み取ることができます。)

揮発性相互作用は、実際のマシンの外部世界との相互作用です。これは、「抽象マシン」に従う必要があります。これらは、プログラム部分と他のプログラム部分との内部的な相互作用ではありません。コンパイラは、それが知っていること、つまり内部プログラム部分についてのみ推論できます。

揮発性アクセスのコード生成は、そのメモリ位置との最も自然な相互作用に従う必要があります。当然のことです。つまり、一部の揮発性アクセスはアトミックであると予想されますlongの表現を自然に読み書きする場合、アーキテクチャはアトミックであり、コンパイラーは揮発性オブジェクトにバイトごとにアクセスするために愚かな非効率的なコードを生成してはならないため、volatile longの読み取りまたは書き込みはアトミックであることが期待されます。例

アーキテクチャを理解することでそれを判断できるはずです。 volatileはコンパイラーが透過的であるべきであることを意味するため、コンパイラーについて何も知る必要はありません

しかし、volatileは、特定の場合に最適化された最小限のメモリ操作を実行するために、予想されるAssemblyの出力を強制するだけです。

一般的なケースは、コンストラクトに関する情報がない場合にコンパイラーが行うことです:f.ex。ダイナミックディスパッチを介して左辺値で仮想関数を呼び出すのは一般的なケースであり、コンパイル時に式で指定されたオブジェクトのタイプを特定した後、オーバーライドを直接呼び出します。コンパイラーは常にすべての構造の一般的なケース処理を行い、ABIに従います。

Volatileは、スレッドの同期や「メモリの可視性」を提供するために特別なことは何もしません。volatileは、実行中または停止したスレッド内から見た抽象レベルでのみ保証を提供します。つまり、CPUコアの内部

  • volatileは、どのメモリ操作がメインに到達するかについては何も述べていませんRAM(これらの保証を取得するために、アセンブリ命令またはシステムコールで特定のメモリキャッシングタイプを設定できます);
  • volatileは、メモリ操作がいつでも(L1でもない)キャッシュのレベルにコミットされることを保証しません

2番目の点のみが、揮発性はほとんどのスレッド間通信の問題で役に立たないことを意味します。最初の点は、CPU外のハードウェアコンポーネントとの通信を含まず、メモリバス上にあるプログラミングの問題には本質的に関係ありません。

スレッドを実行するコアの観点から保証された動作を提供する揮発性のプロパティは、そのスレッドの実行順序の観点から実行される、そのスレッドに配信される非同期信号が、ソースコードの順序で操作を表示することを意味します。

スレッドにシグナルを送信することを計画していない限り(以前に合意された停止ポイントがない現在実行中のスレッドに関する情報を統合する非常に便利なアプローチ)、揮発性は適していません。

1
curiousguy