0

少し前に就職の面接で聞かれた質問があります。私はデータ プロセッサのキャッシュについてさまよっていました。質問自体は揮発性変数に関連していました。これらの変数のメモリアクセスを最適化できないのはなぜですか。私の理解では、volatile 変数を読み取るときは、プロセッサ キャッシュを省略する必要があります。そして、これが私の質問です。そのような場合、そのような変数へのアクセスが実行されると、キャッシュ全体がフラッシュされますか? または、メモリ領域のキャッシュを省略すべきレジスタ設定がありますか? または、キャッシュを参照せずにメモリを読み取る機能はありますか? または、アーキテクチャに依存していますか。

お時間とご回答ありがとうございます。

4

2 に答える 2

2

ここにはいくつかの混乱があります。プログラムが (コンパイラを介して) 使用するメモリは、実際には抽象化されており、OS とプロセッサによって一緒に維持されます。そのため、ページング、スワッピング、物理アドレス空間、およびパフォーマンスについて心配する必要はありません。

コードを最適化するときに、実際に何が起こるかを知りたいと思うかもしれないので、あなたを支援する一連のツールがあります( SW のプリフェッチなど) と、システムがどのように機能するか (キャッシュのサイズと階層) に関する大まかなアイデアを提供し、最適化されたコードを記述できるようにします。しかし、私が言ったように、あなたはこれについて心配する必要はありません. たとえば、キャッシュは、共有データ (一連の非常に複雑な HW プロトコルによって維持される) を操作する場合や、仮想アドレス エイリアス (同じ物理アドレスを指す複数の仮想アドレス) の場合でも一貫性を維持することが保証されます。しかし、ここで「ある程度」が来ます 一部 - 場合によっては、正しく使用していることを確認する必要があります。たとえば、メモリにマップされた IO を実行する場合は、適切に定義して、プロセッサがキャッシュされるべきではないことを認識できるようにする必要があります。コンパイラが暗黙的にこれを行う可能性は低く、おそらく知らないでしょう。

現在、volatile上位レベルに住んでいます。これは、プログラマーとコンパイラーの間の契約の一部です。これは、コンパイラがこの変数を使用してあらゆる種類の最適化を行うことを許可されていないことを意味します。これは、メモリ モデルの抽象化内であっても、プログラムにとって安全ではありません。. これらは基本的に、(割り込み、mmio、他のスレッドなどを介して) 任意の時点で値を外部から変更できる場合です。コンパイラーは、メモリに何かを書き込んだり読み取ったりすることを決定した場合、メモリの抽象化の上にまだ存在することに注意してください。可能なヒントは別として、このメモリのチャンクを手元に近づけるために必要なことはすべてプロセッサに完全に依存しています。正しさを保つ。ただし、コンパイラーは HW よりもはるかに自由度が高く、読み取り/書き込みを移動したり、変数をまとめて削除したりすることができますが、これはほとんどの場合 CPU では許可されていないため、安全でない場合はそれを防止する必要があります。 . それが起こるときのいくつかの良い例はここにあります - http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword

したがって、揮発性ヒントはメモリ モデル内のコンパイラの自由を制限しますが、基になるハードウェアを必ずしも制限するわけではありません。あなたはおそらくそれを望んでいないでしょう - あなたが他のスレッドに公開したい揮発性変数を持っているとしましょう - コンパイラがそれをキャッシュ不可にすると、パフォーマンスが損なわれます (そして必要なしに)。それに加えて、メモリ モデルを安全でないキャッシュから保護したい場合 (これは volatile が役立つケースのサブセットにすぎません)、明示的に保護する必要があります。

編集:例を追加しないのは気分が悪いので、わかりやすくするために、次のコードを検討してください。

int main() {
    int n = 20;
    int sum = 0;
    int x = 1;
    /*volatile */ int* px = &x;

    while (sum < n) {
        sum+= *px;
        printf("%d\n", sum);
    }
    return 0;
}

これは、1 である x のジャンプで 1 から 20 までカウントされますgcc -O3

0000000000400440 <main>:
  400440:       53                      push   %rbx
  400441:       31 db                   xor    %ebx,%ebx
  400443:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  400448:       83 c3 01                add    $0x1,%ebx
  40044b:       31 c0                   xor    %eax,%eax
  40044d:       be 3c 06 40 00          mov    $0x40063c,%esi
  400452:       89 da                   mov    %ebx,%edx
  400454:       bf 01 00 00 00          mov    $0x1,%edi
  400459:       e8 d2 ff ff ff          callq  400430 <__printf_chk@plt>
  40045e:       83 fb 14                cmp    $0x14,%ebx
  400461:       75 e5                   jne    400448 <main+0x8>
  400463:       31 c0                   xor    %eax,%eax
  400465:       5b                      pop    %rbx
  400466:       c3                      retq   

注意してくださいadd $0x1,%ebx-変数はコンパイラによって十分に「安全」と見なされているため(ここでは揮発性がコメントアウトされています)、ループ不変と見なすことができます。実際、各反復で何かを印刷していなければ、gcc は最終結果を非常に簡単に判断できるため、ループ全体が最適化されていたはずです。

ただし、 volatile キーワードのコメントを外すと、次のようになります -

0000000000400440 <main>:
  400440:       53                      push   %rbx
  400441:       31 db                   xor    %ebx,%ebx
  400443:       48 83 ec 10             sub    $0x10,%rsp
  400447:       c7 04 24 01 00 00 00    movl   $0x1,(%rsp)
  40044e:       66 90                   xchg   %ax,%ax
  400450:       8b 04 24                mov    (%rsp),%eax
  400453:       be 4c 06 40 00          mov    $0x40064c,%esi
  400458:       bf 01 00 00 00          mov    $0x1,%edi
  40045d:       01 c3                   add    %eax,%ebx
  40045f:       31 c0                   xor    %eax,%eax
  400461:       89 da                   mov    %ebx,%edx
  400463:       e8 c8 ff ff ff          callq  400430 <__printf_chk@plt>
  400468:       83 fb 13                cmp    $0x13,%ebx
  40046b:       7e e3                   jle    400450 <main+0x10>
  40046d:       48 83 c4 10             add    $0x10,%rsp
  400471:       31 c0                   xor    %eax,%eax
  400473:       5b                      pop    %rbx
  400474:       c3                      retq   
  400475:       90                      nop

add オペランドがスタックから読み込まれるようになりました。これは、コンパイラが誰かがそれを変更したのではないかと疑うようになるためです。それはまだキャッシュであり、通常のライトバック タイプのメモリと同様に、別のスレッドまたは DMA からの変更の試みをキャッチし、メモリ システムが新しい値を提供します (ほとんどの場合、キャッシュ ラインはスヌープされて無効化され、CPU現在所有しているコアから新しい値を取得します)。ただし、私が言ったように、x が通常のキャッシュ可能なメモリ アドレスではなく、何らかの MMIO か、メモリ システムの下で静かに変更される可能性のある何かである場合は、キャッシュされた値が間違っていることになります (そのため、MMIO を使用する必要があります)。 t はキャッシュされません)、たとえそれが揮発性と見なされていても、コンパイラはそれを決して知りません。

ちなみに、volatile int x直接使用して追加しても同じ結果になります。繰り返しになりますが、x または px グローバル変数を作成することもできます。その理由は、コンパイラーは、誰かがアクセスした可能性があると推測するため、明示的な volatile ヒントの場合と同じ予防措置を講じるからです。興味深いことに、同じことが x をローカルにする場合にも当てはまりますが、そのアドレスをグローバル ポインターにコピーします (ただし、メイン ループでは x を直接使用します)。コンパイラは非常に慎重です。100% 完全な証明であるとは言えません。理論的には x をローカルに保持し、コンパイラに最適化を実行させてから、外部からどこかのアドレスを「推測」することができます (たとえば、別のスレッド)。これは、揮発性が重宝するときです。

于 2013-09-24T23:42:21.050 に答える
0

volatile variable, how can we not optimize the memory access for those variables.

はい、Volatileon variable は、プログラマーがプログラムの範囲外でこの変数に何が起こるかを予測でき、コンパイラーには見えないような方法で変数を読み書きできることをコンパイラーに伝えます。これは、コンパイラが目的の機能を変更する変数に対して最適化を実行できないことを意味し、その値をレジスタにキャッシュして、各反復中にレジスタコピーを使用してメモリアクセスを回避します。

`entire cache being flushed when the access for such variable is executed?`

いいえ。理想的には、CPU とメモリの間の既存のキャッシュ エントリをフラッシュしない、変数の格納場所からコンパイラが変数にアクセスします。

Or there is some register setting that caching should be omitted for a memory region?

どうやら、レジスタがキャッシュされていないメモリ空間にある場合、そのメモリ変数にアクセスすると、キャッシュメモリからよりも最新の値が得られます。繰り返しますが、これはアーキテクチャに依存する必要があります。

于 2013-09-24T19:00:44.460 に答える