web-dev-qa-db-ja.com

キャッシュラインはどのように機能しますか?

プロセッサがキャッシュラインを介してキャッシュにデータを取り込むことを理解しています。たとえば、Atomプロセッサでは、読み取られる実際のデータのサイズに関係なく、一度に約64バイトを取り込みます。

私の質問は:

メモリから1バイトを読み込む必要があると想像してください。どの64バイトがキャッシュに取り込まれますか?

私が見ることができる2つの可能性は、64バイトが関心のあるバイトの下の最も近い64バイト境界で始まるか、64バイトが事前に決められた方法でバイトの周りに広がっていることです(たとえば、半分の下、半分の上、または中でも)。

どっち?

145
Norswap

ロードするバイトまたはWordを含むキャッシュラインがキャッシュにまだ存在しない場合、CPUはキャッシュライン境界で始まる64バイトを要求します(必要なアドレスの下の最大アドレスは64の倍数です) 。

最新のPCメモリモジュールは、一度に64ビット(8バイト)を転送します。 8回の転送のバーストで なので、1つのコマンドがメモリからの完全なキャッシュラインの読み取りまたは書き込みをトリガーします。 (DDR1/2/3/4 SDRAMバースト転送サイズは最大64Bまで構成可能です。CPUはキャッシュラインサイズに合わせてバースト転送サイズを選択しますが、64Bが一般的です)

経験則として、プロセッサがメモリアクセスを予測できない(およびプリフェッチする)場合、検索プロセスには〜90ナノ秒、または〜250クロックサイクル(アドレスを認識しているCPUからデータを受信するCPUまで)かかります。

対照的に、L1キャッシュのヒットは、3または4サイクルのロード使用レイテンシーを持ち、最新のx86 CPUでは、ストアリロードのストア転送レイテンシーは4または5サイクルです。他のアーキテクチャでも同様です。

さらに読む:Ulrich Drepper's すべてのプログラマーがメモリについて知っておくべきこと 。ソフトウェアプリフェッチのアドバイスは少し時代遅れです。現代のHWプリフェッチャーはよりスマートで、ハイパースレッディングはP4日間よりもはるかに優れています(したがって、プリフェッチスレッドは通常無駄です)。また、 x86 タグwikiには、そのアーキテクチャ用のパフォーマンスリンクが多数あります。

111
Eugene Smith

キャッシュラインの幅が64バイトの場合、64で割り切れるアドレスで始まるメモリブロックに対応します。アドレスの最下位6ビットは、キャッシュラインへのオフセットです。

そのため、任意のバイトについて、フェッチする必要があるキャッシュラインは、アドレスの最下位6ビットをクリアすることで見つけることができます。これは、64で割り切れる最も近いアドレスに切り捨てることに対応します。

これはハードウェアによって行われますが、いくつかの参照Cマクロ定義を使用して計算を表示できます。

#define CACHE_BLOCK_BITS 6
#define CACHE_BLOCK_SIZE (1U << CACHE_BLOCK_BITS)  /* 64 */
#define CACHE_BLOCK_MASK (CACHE_BLOCK_SIZE - 1)    /* 63, 0x3F */

/* Which byte offset in its cache block does this address reference? */
#define CACHE_BLOCK_OFFSET(ADDR) ((ADDR) & CACHE_BLOCK_MASK)

/* Address of 64 byte block brought into the cache when ADDR accessed */
#define CACHE_BLOCK_ALIGNED_ADDR(ADDR) ((ADDR) & ~CACHE_BLOCK_MASK)
20
Kaz

まず第一に、メインメモリアクセスは非常に高価です。現在、2GHz CPU(最低速度)は1秒あたり2Gティック(サイクル)です。 CPU(最近の仮想コア)は、ティックごとにレジスタから値をフェッチできます。仮想コアは複数の処理ユニット(ALU-算術論理ユニット、FPUなど)で構成されるため、可能であれば特定の命令を実際に並列処理できます。

メインメモリへのアクセスには約70ns〜100nsかかります(DDR4はわずかに高速です)。今回は基本的にL1、L2、およびL3キャッシュを検索し、メモリをヒットする(メモリコントローラにコマンドを送信し、メモリバンクに送信する)後、応答を待って完了します。

100nsは約200ティックを意味します。したがって、基本的にプログラムが各メモリがアクセスするキャッシュを常にミスすると、CPUはメモリを待機する時間(メモリを読み取るだけの場合)の約99,5%をアイドル状態で費やします。

速度を上げるために、L1、L2、L3キャッシュがあります。チップに直接配置されたメモリを使用し、特定のビットを格納するために異なる種類のトランジスタ回路を使用します。 CPUは通常、より高度な技術を使用して生産され、L1、L2、L3メモリの生産障害はCPUを価値のない(欠陥)にする可能性があるため、これはメインメモリよりも多くのスペース、エネルギー、コストがかかりますL1、L2、L3キャッシュが大きいとエラー率が増加し、歩留まりが低下してROIが直接低下します。そのため、使用可能なキャッシュサイズに関しては大きなトレードオフがあります。

(現在、特定の部分を非アクティブ化して、実際の生産上の欠陥がキャッシュメモリ領域である可能性を減らして、全体としてCPUの欠陥をレンダリングできるようにするために、より多くのL1、L2、L3キャッシュを作成します)。

タイミングのアイデアを提供するには(ソース: キャッシュとメモリにアクセスするためのコスト

  • L1キャッシュ:1nsから2ns(2-4サイクル)
  • L2キャッシュ:3ns〜5ns(6〜10サイクル)
  • L3キャッシュ:12ns〜20ns(24〜40サイクル)
  • RAM:60ns(120サイクル)

異なるCPUタイプを混在させているため、これらは単なる推定値ですが、メモリ値がフェッチされ、特定のキャッシュレイヤーでヒットまたはミスが発生した場合に実際に何が起こっているかを知ることができます。

したがって、キャッシュは基本的にメモリアクセスを大幅に高速化します(60ns対1ns)。

値を取得し、キャッシュに保存して再読み取りする機会があるため、頻繁にアクセスされる変数には適していますが、メモリコピー操作の場合は、値を読み取るだけで、どこかに値を書き込み、値を読み取らないため、依然として遅くなります繰り返しますが、キャッシュヒットはなく、デッドスローです(これに加えて、順不同の実行があるため、並行して発生する可能性があります)。

このメモリコピーは非常に重要であるため、高速化するさまざまな方法があります。初期の頃は、メモリはCPUの外部でメモリをコピーすることができました。メモリコントローラーによって直接処理されたため、メモリコピー操作はキャッシュを汚染しませんでした。

しかし、プレーンなメモリコピーの他に、メモリの他のシリアルアクセスは非常に一般的でした。例は、一連の情報の分析です。整数の配列を持ち、合計、平均、平均、またはより単純な特定の値(フィルター/検索)を計算することは、汎用CPUで毎回実行される別の非常に重要なアルゴリズムのクラスでした。

そのため、メモリアクセスパターンを分析することにより、データが非常に頻繁にシーケンシャルに読み取られることが明らかになりました。プログラムがインデックスiの値を読み取ると、プログラムは値i + 1も読み取る可能性が高くなりました。この確率は、同じプログラムが値i + 2などを読み取る確率よりもわずかに高くなります。

したがって、メモリアドレスが与えられた場合、先読みして追加の値をフェッチすることをお勧めします(今でも有効です)。これが、ブーストモードがある理由です。

ブーストモードでのメモリアクセスとは、アドレスが送信され、複数の値が順次送信されることを意味します。追加の値を送信するたびに、さらに約10ns(またはそれ以下)しかかかりません。

別の問題は住所でした。住所の送信には時間がかかります。メモリの大部分をアドレス指定するには、大きなアドレスを送信する必要があります。初期の頃は、アドレスバスが1サイクル(ティック)でアドレスを送信するのに十分な大きさではなく、アドレスを送信するためにより多くの遅延を追加するために複数のサイクルが必要でした。

たとえば、64バイトのキャッシュラインは、メモリがサイズが64バイトのメモリの別個の(重複しない)ブロックに分割されることを意味します。 64バイトは、各ブロックの開始アドレスの最下位6アドレスビットが常にゼロになることを意味します。したがって、これらの6個のゼロビットを毎回送信する必要はなく、アドレスバス幅の数に関係なく、アドレス空間を64倍に増やす必要があります(歓迎効果)。

キャッシュラインが解決する別の問題(先読みとアドレスバス上の6ビットの保存/解放以外)は、キャッシュの構成方法にあります。たとえば、キャッシュが8バイト(64ビット)ブロック(セル)に分割される場合、このキャッシュセルが値を保持するメモリセルのアドレスを格納する必要があります。アドレスが64ビットの場合、これはキャッシュサイズの半分がアドレスによって消費され、100%のオーバーヘッドが発生することを意味します。

キャッシュラインは64バイトであり、CPUは64ビットを使用する可能性があるため、6ビット= 58ビット(ゼロビットを正しく格納する必要はありません)は、58ビット(11%のオーバーヘッド)で64バイトまたは512ビットをキャッシュできることを意味します。実際には、保存されるアドレスはこれよりもさらに小さくなりますが、ステータス情報があります(キャッシュラインが有効で正確で、ダーティであり、RAMに書き戻す必要があるなど)。

もう1つの側面は、セットアソシエイティブキャッシュがあることです。すべてのキャッシュセルが特定のアドレスを格納できるわけではなく、それらのサブセットのみを格納できます。これにより、必要な保存アドレスビットがさらに小さくなり、キャッシュの並列アクセスが可能になります(各サブセットは1回アクセスできますが、他のサブセットには依存しません)。

特に、異なる仮想コア、コアごとの独立した複数の処理ユニット、および最終的に1つのメインボード上の複数のプロセッサ(48個以上のプロセッサを収容するボードがある)の間でキャッシュ/メモリアクセスを同期する場合にあります。

これが基本的にキャッシュラインがある理由です。先読みの利点は非常に高く、キャッシュラインから1バイトを読み取り、残りを再度読み取らないという最悪のケースは、確率が非常に小さいため非常にわずかです。

キャッシュラインのサイズ(64)は、より大きなキャッシュライン間の賢明に選択されたトレードオフであり、キャッシュラインの最後のバイトが近い将来、完全なキャッシュラインを取得するのにかかる時間も読み込まれないメモリから(および書き戻すため)、キャッシュ構成のオーバーヘッド、キャッシュとメモリアクセスの並列化も含まれます。

16
Martin Kersten

プロセッサにはマルチレベルキャッシュ(L1、L2、L3)があり、これらはサイズと速度が異なります。

ただし、各キャッシュに何が入るのかを正確に理解するには、その特定のプロセッサで使用される分岐予測子と、プログラムの命令/データがそれに対してどのように動作するかを調べる必要があります。

分岐予測子CPUキャッシュ 、および 置換ポリシー についてお読みください。

これは簡単な作業ではありません。一日の終わりにパフォーマンステストだけが必要な場合は、 Cachegrind のようなツールを使用できます。ただし、これはシミュレーションであるため、結果はある程度異なる場合があります。

6
jweyrich

すべてのハードウェアが異なるため、はっきりとは言えませんが、CPUにとって非常に高速で単純な操作であるため、通常は「下の最も近い64バイト境界で64バイトが開始」です。

4
bramp