メモリバリア解析


「メモリバリア」の4文字を見たときの最初の反応は何ですか?レジスタから誤った値が出ましたか?ifence、sfenceとかの指令?それともvolatileなどのキーワードですか?はい、私が初めてこの4つの字を見たとき、頭の中に浮かんだのは魔獣が覇権を争う中で緑の苔が敷かれた岩の障壁です.そして、メモリの障壁が具体的に何なのかを理解して、それによく知っていると思ってから、私の最初の反応はあの緑の油の石で、Aに行きたいと思っていました.
本題に戻り、メモリバリアとは何かを説明します.メモリバリアとは、「コンパイラの最適化とキャッシュの使用により、メモリへの書き込み操作がタイムリーに反応しない、すなわちメモリへの書き込み操作が完了した後に読み取られるのは古い内容である可能性がある」(『独自の道のコア』より抜粋)という.(ここでは概念が正確ではないようで、正確な定義:コンパイラとハードウェアの不正確な最適化を防止するために、メモリへのアクセス順序(実は変数)と書き込みプログラム時のアクセス順序が一致しないように提案された解決策である.それは間違いの現象ではなく、間違いの現象に対する解決策である.
概念は概念で、硬いもの、知っている人はそこから何かを悟ることができて、知らない人はまだ霧の水です.焦らないで、私たちはまずメモリのバリアに分類して、それから1つずつ研究して、この文章を見終わってから、帰って概念を読んで、あなたは理解しました!
メモリバリアの分類:
コンパイラによるメモリバリアキャッシュによるメモリバリア乱順実行によるメモリバリア1、コンパイラによるメモリバリア:
レジスタから1つの数を取るのはメモリから取るよりも速いことはよく知られています.そのため、コンパイラが最適化度の高いプログラムをコンパイルするために、一般的な変数をレジスタに入れ、次回この変数を使用するときはメモリにアクセスせずにレジスタから直接取ります.これは問題が発生します.他のスレッドがメモリの値を変更したらどうしますか?コンパイラがどうしてそんなに愚かなのか、このような低級な間違いを犯すのかと思うかもしれません.はい、コンパイラはあなたが想像していたほど頭がいいわけではありません.次のコードを見てみましょう.(コードは『独自の道を切り開くコア』)
int flag=0;
 
void wait(){
    while ( flag == 0 )
        sleep(1000);
    ......
}
 
void wakeup(){
    flag=1;
}

このコードは、あるスレッドが別のスレッドがflagを変更するのをループで待っていることを示します.Gccなどのコンパイラはコンパイル時にsleep()がflagの値を修正しないことを発見したので,効率を高めるためにあるレジスタをflagに割り当てることになり,コンパイル後にこのような擬似アセンブリコードを生成した.
void wait(){
    movl  flag, %eax;
 
    while ( %eax == 0)
        sleep(1000);
}

このとき、wakeup関数がflagの値を修正すると、wait関数はまだバカなレジスタの値を読んでいて、実際にflagが変わったことを知らないと、スレッドは死んでしまいます.これにより、コンパイラの最適化は逆の効果をもたらします.
しかし、コンパイラにこの最適化を放棄させるとは言えません.多くの場合、この最適化がもたらす性能は非常に大きいからです.どうすればいいの?このような状況を避ける方法はありますか?答えは肯定的でなければなりません.キーワードvolatileを使ってこのような状況を避けることができます.
volatile int flag = 0;

これにより、コンパイラがflagにレジスタを割り当てることを避けることができます.
はい、上記の説明は、「コンパイラ最適化によるメモリバリア」と呼ばれていますが、何か分かりましたか?概念を見てみましょうか?
2、キャッシュによるメモリバリア
はい、レジスタがこのような問題を引き起こすことができる以上、キャッシュは?CPUはデータをcacheという場所に取り出し、次に取るときに直接cacheにアクセスし、書き込むときにも値をcacheに書き込むことを知っています.
では、まず、単核の場合に問題が発生するかどうかを考えてみましょう.まず考えてみると、シングルコアの場合、CPU以外にメモリを変更するものは何ですか?ところで、外部機器のDMAです!では、DMAがメモリを修正すると、メモリバリアの問題が発生するのではないでしょうか.答えは、現在のアーキテクチャでは、できません.
外部装置のDMA操作が終了すると、CPUが対応するキャッシュ行が失効したことを知るメカニズムがある.CPUがDMA操作を開始すると、外部機器から起動命令を送信したい前に、対応するcacheの内容をメモリに書き込む必要がある.ほとんどのRISCのアーキテクチャでは,このメカニズムは特殊な命令を書くことによって実現される.X 86では、バスモニタリング技術と呼ばれる方法を用いて実現される.つまり、CPUと外部機器がメモリにアクセスする際には、バスの仲介を経て、cache内のメモリ領域を記録する専用のハードウェアモジュールがあり、外部機器がメモリに書き込むと、このハードウェアでメモリ領域がcache内にあるかどうかを判断し、対応する操作を行う.
では、cacheによるメモリバリアはいつ発生するのでしょうか.マルチCPU?はい、マルチCPUのシステムでは、それぞれのCPUに独自のcacheがあり、同じメモリ領域が2つのCPUのcacheに同時に存在する場合、CPU 1は自分のcacheの値を変更しますが、CPU 2は依然として自分のcacheでその古い値を読み取り、この結果はカップではないでしょうか.アクセス操作がないので、バスも監視できませんが、この時はどうすればいいですか?
そうですね.どうすればいいですか.CPU 2の読み取り操作の前に自分のcacheを無効にする必要があります.x 86では、lock接頭辞の命令、cpuid、iretなど、多くの命令ができます.カーネルにはmb(),rmb(),wmb()という関数がいくつか使用されています.以上のコマンドを使っているので、カーネルコードを見てみましょう.
3、乱順実行によるメモリバリア:
スーパースカラープロセッサがますます流行していることはよく知られていますが、コアさえ4つの発光です.ハイパースケールは実際にはCPUが複数の独立した流水線を持っており、一度に複数の命令を送信することができるため、多くの命令の乱順実行を許可し、具体的にどのような乱順方法で、アーキテクチャの本を読むことができます.ここではメモリバリアと言います.
命令乱順が実行されると問題が発生し,命令1があるメモリに値を割り当て,命令2がそのメモリから値を取って演算に用いると仮定する.もし彼ら2人が逆転したら、命令2が先にメモリから値を取って演算するのは間違っているのではないでしょうか.
この場合、x 86にはlfence、sfence、およびmfence命令が設けられ、流水ラインを停止する.
lfence:関連するパイプラインを停止し、lfenceを知る前にメモリに対する読み取り操作指令をすべて完了する
sfence:関連するパイプラインを停止し、lfenceを知る前にメモリに対する書き込み操作命令をすべて完了する
mfence:関連流水線を停止し、lfenceを知る前にメモリに対する読み書き操作指令をすべて完了
はい、この3つのタイプを終えて、概念を見て、はっきりしましたか?もしまだ分からないなら、それは私の表現能力が限られているので、自分でネット上で探してみましょう.