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