プログラミング
CPUにおけるメモリ順序
Memory Ordering in CPUs (fgiesen.wordpress.com)
要約
CPUのメモリ順序に関する一般的な誤解は、CPUが常にメモリモデルに厳密に従うというものです。実際には、多くのCPUは最適化のために順序を緩和しており、これは強順序・弱順序アーキテクチャのどちらでも同様です。重要なのは、競合が発生した場合にどのように処理するかであり、弱順序の方が緩和された順序を許容する傾向があります。
全文翻訳
CPUにおけるメモリ順序について、強順序アーキテクチャ(x86やSPARCなど)と弱順序アーキテクチャ(ARMやRISC-Vなど)の違いについて、弱順序の方が本質的にスケーラビリティが高いという確信に満ちた主張をしばしば目にします。しかし、ここには根本的な誤解があります。それは、あらゆるCPUが、すべてのメモリアクセスにおいて実際にメモリモデルに従うという暗黙の仮定です。彼らは、そう振る舞うことを約束するのです。この一見些細な区別には、世界の差があります。
CPUコアの中には、実際にアーキテクチャのメモリ順序規則を文字通り遵守するものがあることは確かです。しかし、この種の動作は通常、キャッシュを持たない、小さなコアやマイクロコントローラーに限定されます。それ以外のほとんどのもの(そしてそれには、より大きなインオーダー設計も含まれます!)は、いくつかの手抜きをします。
具体的には、通常、メモリ順序は楽観的に実装されます。ほとんどのロードは、最近他のコアによって変更されていない(または変更が進行中ではない)データをアクセスし、ほとんどのストアは競合していないと想定されます。もしすべてのデータが、命令がパイプラインに入った時点からコミットされるまでの間に変更されていないメモリからロードされるのであれば、どのような順序でも実行できます(そしてアウトオブオーダーCPUはまさにそれを実行します)。もしストアが競合していない(つまり、同じキャッシュラインに同時に書き込もうとするCPUが2つない)のであれば、そして誰もその結果を見ないのであれば、それらも同様に自由に並べ替えることができます。そして、ほとんどのCPUは、ほとんどの場合、まさにこれを行います。
もちろん、これらの順序規則が違いを生むこともあります。そうでなければ、そもそもそれらは存在しないでしょう。しかし、これはアクセスが競合する場合にのみ重要になります。つまり、私たちが実際にメモリにアクセスしてから、そのメモリアクセス命令がコミットされる(命令パイプラインのかなり後の方で、命令が「公式」になり、その状態が外部から見えるようになる時点)までの間に、誰かが私たちが関心のあるメモリ(または少なくともそれに含まれるキャッシュライン)を変更した場合です。コミット前は、問題があったことが判明した場合、命令はロールバックされる可能性があります。競合はそのような問題の一つです(例外/トラップ、割り込み、誤予測された分岐などと並んで)。
したがって、実際の СPUの実装は、「信頼はするが検証もする」アプローチに近いものです。ほとんどすべてのメモリアクセスは、ほとんどの場合競合しないと想定し、これが命令の実行方法を決定します。しかし、後からメモリ順序違反を検出するために、十分なメタデータを保持しておきます。違反が検出された場合、問題の命令はコミットできず、プログラムの状態は実行直前の状態にロールバックされ、再試行されます。これが、弱順序かどうかにかかわらず、誰もが行っている方法です。
したがって、メモリモデル間の違いは、強順序モデルを持つマシンがすべてのメモリアクセスをメモリモデルの規則に従って実行し、弱モデルを持つマシンがそうしないということではありません。それは、競合が発生した場合(つまり、私たちの目的のためには、インフライトの命令がアクセスしたメモリを別のコアが変更するような外部エージェント)、強順序マシンの方が弱順序マシンよりも衝突を報告し(そして再試行する)可能性が高いということです。さらに、競合はどこでも遅いケースです。弱順序マシンはこの点でいくつかの利点がありますが、実際にはあまり役に立たないことが多いです(少なくとも私は実際のワークロードで大きな利点を見たことはありません)。マルチスレッドコードに関する私の個人的なモットーは、「競合を減らす」ことであり、「より速く競合する」ことではありません。あなたの経験は異なるかもしれません。
強順序マシンと弱順序マシンの大きな違いは、前者がすべてのメモリ操作を厳密に順序通りに実行し、後者がそうしないということではなく、前者はインフライトのメモリ操作ごとに順序違反がないかチェックするための十分なメタデータを保持する必要があるのに対し、後者は主にメモリバリアとの順序付けが必要なリラックスしたロード/ストアを扱います。また、競合の場合、弱順序マシンの方が強順序マシンよりも多くのメモリアクセス順序が合法である(したがって、完了してもよい)ことを意味します。これらのことは確かに実装を制約し、x86のTSOのようなメモリモデルのコストはゼロではありませんが、「x86とSPARCはすべてのメモリ操作を順序通りに実行する必要があり、ARMとRISC-V CPUはそうではない」というよりもはるかにニュアンスがあります。コストはありますが、それを測定するのは簡単ではありません。
さらに、経験的なデータとして、現在では数百のCPUを搭載したサーバーシステムがあり、弱順序(主にARM)と強順序(主にx86)の両方の種類があります。これらは両方とも、広範には同じ特性を示します。それらは「共有なし」タイプのワークロードではうまく機能し、使いこなすのが非常に難しいNUMAセットアップに偏り、実際の競合のボトルネックはあなたの日常を完全に台無しにします。ほとんどの場合、多数のコアを持つマシンを扱うことから得られる私の主な教訓は、いつものように、最先端は惨めな場所であり、現在市販されている最大数のCPUソケットの半分程度のCPUコアを持つセットアップを扱う方がずっと楽しいということです。
違いがないということではありません。明らかに違いはあります。しかし、このトピックに関する言説は、弱メモリモデルを持つCPUは、多数のコアを持つCPUでのヘビーマルチスレッドワークロードにおいて、はるかに優れたパフォーマンス(または少なくともはるかに電力効率が良い)を発揮するはずだということを示唆していますが、それは私の経験ではありません。それは、スケーリングするアプローチとそうでないアプローチの明確な区別というよりも、異なる強みと弱みを持つ異なるベンダーのCPU uArch間の違いに近いものです。