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


167

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

私の質問は:

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

私が見ることができる2つの可能性は、64バイトが対象のバイトより下の最も近い64バイトの境界から始まるか、または64バイトが所定の方法でバイトの周りに広がる(たとえば、半分下、半分上、または中でも)。

どっち?


22
これを読んでください:すべてのプログラマーがメモリについて知っておくべきこと。その後、もう一度お読みください。より良い(pdf)ソースはこちら
andersoj

回答:


128

ロードしているバイトまたはワードを含むキャッシュラインがまだキャッシュに存在しない場合、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の「すべてのプログラマーがメモリについて知っておくべきこと」。ソフトウェアのプリフェッチアドバイスは少し時代遅れです。最新のHWプリフェッチャーはよりスマートで、ハイパースレッディングはP4日よりもはるかに優れています(そのため、プリフェッチスレッドは一般的に無駄です)。また、 タグwikiには、そのアーキテクチャーに関する多くのパフォーマンスリンクがあります。


1
この答えは全く意味がありません。64ビットのメモリ帯域幅(この点でも間違っています)は、64バイト(!)のビットではなく、何をするのですか?また、Ramにヒットした場合、10〜30 nsも完全に間違っています。L3またはL2キャッシュには当てはまるかもしれませんが、90nsに近いRAMには当てはまりません。つまり、バースト時間-バーストモードで次のクワッドワードにアクセスする時間(実際には正しい答えです)
Martin Kersten

5
@MartinKersten:DDR1 / 2/3/4 SDRAMの1チャネルは64ビットのデータバス幅を使用します。キャッシュライン全体のバースト転送には、それぞれ8Bの転送が8回必要ですが、実際に行われるのはこのためです。最初に目的のバイトを含む8Bに揃えられたチャンクを転送することでプロセスが最適化されることは正しいかもしれません。つまり、そこからバーストを開始します(そして、バースト転送サイズの最初の8Bでなければラップアラウンドします)。ただし、マルチレベルキャッシュを備えた最近のCPUでは、バーストの最初のブロックをL1キャッシュに早期に中継することになるため、おそらくもうそれを行わないでしょう。
Peter Cordes

2
HaswellはL2とL1Dキャッシュの間に64Bパス(つまり、全キャッシュライン幅)を持っているため、要求されたバイトを含む8Bを転送すると、そのバスの非効率的な使用につながります。@Martinは、メインメモリに移動する必要があるロードのアクセス時間についても正しいです。
Peter Cordes

3
データが一度にメモリ階層全体に到達するかどうか、またはL3がメモリからのフルラインを待ってからL2への送信を開始するかどうかについての良い質問です。異なるレベルのキャッシュ間に転送バッファーがあり、未処理のミスはそれぞれ1つを要求します。したがって、(推測による)おそらくL3は、メモリコントローラーからのバイトを独自の受信バッファーに入れると同時に、必要なL2キャッシュ用の適切なロードバッファーにバイトを入れます。ラインがメモリから完全に転送されると、L3はL2にラインの準備ができていることを通知し、それを独自のアレイにコピーします。
Peter Cordes 2016

2
@Martin:先に進んでこの回答を編集することにしました。今はもっと正確で、まだ簡単だと思います。:将来のリーダーは:またMike76の質問と私の答えを参照stackoverflow.com/questions/39182060/...
ピーター・コルド

22

キャッシュラインが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)

1
これを理解するのに苦労しています。私は2年後だと知っていますが、これのサンプルコードを教えてもらえますか?1行または2行。
Nick

1
@Nickこのメソッドが機能する理由は、2進数システムにあります。2の累乗では、ビットセットが1つだけ残り、残りのすべてのビットがクリアされます。したがって、64の場合0b1000000、最後の6桁がゼロであることに注意してください。 %64)、それらをクリアすると、最も近い64バイト境界で整列されたメモリアドレスが得られます。
legends2k 2015年

21

まず第一に、メインメモリアクセスは非常に高価です。現在、2GHzのCPU(1番遅いの)は1秒あたり2Gティック(サイクル)です。CPU(最近の仮想コア)は、ティックごとに1回、レジスタから値をフェッチできます。仮想コアは複数の処理ユニット(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が直接下がります。したがって、使用可能なキャッシュサイズに関しては、大きなトレードオフがあります。

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

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

  • 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つの0ビットを毎回送信する必要はなく、アドレス空間の数に関係なくアドレス空間を64倍に増やす必要があります(ウェルカム効果)

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

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

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

特に、異なる仮想コア間でのキャッシュ/メモリアクセスの同期に関しては、コアごとに独立した複数のプロセッシングユニットがあり、最後に1つのメインボード(48プロセッサー以上を搭載するボードがあります)に複数のプロセッサーがあります。

これは、基本的にはキャッシュラインが存在する現在の考え方です。先読みすることの利点は非常に高く、キャッシュラインから1バイトを読み取り、残りを二度と読み取らないという最悪のケースは、確率が非常に小さいため非常にスリムです。

キャッシュラインのサイズ(64)は、より大きなキャッシュライン間の賢明な選択のトレードオフであり、近い将来、最後のバイトが読み取られる可能性は低くなります。これは、キャッシュライン全体をフェッチするのにかかる時間です。メモリから(そしてそれを書き戻すために)、キャッシュ構成のオーバーヘッドと、キャッシュとメモリアクセスの並列化。


1
セット連想キャッシュは、いくつかのアドレスビットを使用してセットを選択するため、タグは例よりもさらに短くなる可能性があります。もちろん、キャッシュはセット内のどのデータ配列にどのタグが対応するかを追跡する必要もありますが、通常、セット内のウェイよりも多くのセットがあります。(たとえば、32xB 8ウェイ連想L1Dキャッシュ、64Bライン、Intel x86 CPUの場合:オフセット6ビット、インデックス6ビット。x86-64(現時点では)は48-ビット物理アドレス。ご存知のとおり、下位12ビットがページオフセットであることは偶然ではないため、L1はエイリアスなしでVIPTになる可能性があります。)
Peter Cordes

素晴らしい答えの芽...「いいね」ボタンはどこにありますか?
エドガードリマ2017

@EdgardLima、賛成ボタンではありませんか?
パセリエ

6

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

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

ブランチプレディクタCPUキャッシュ、および置換ポリシーについてお読みください。

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


4

すべてのハードウェアが異なるため、確かなことは言えませんが、CPUにとって非常に高速でシンプルな操作であるため、通常、「64バイトは最も近い64バイト境界から始まります」です。


2
私がすることができ、特定のために言います。適切なキャッシュ設計には、2の累乗のサイズのラインがあり、自然にアラインされます。(例:64Bアライン)。 これは高速でシンプルなだけでなく、文字通り無料です。たとえば、アドレスの下位6ビットを無視するだけです。 キャッシュは多くの場合、さまざまなアドレス範囲でさまざまなことを行います。(たとえば、キャッシュはヒットとミスを検出するためにタグとインデックスを処理し、データの挿入/抽出にキャッシュライン内のオフセットのみを使用します)
Peter Cordes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.