199

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

私の質問は:

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

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

それはどれですか?

4

5 に答える 5

164

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

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

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

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

さらに読む: Ulrich Drepper のWhat Every Programmer Should Know About Memory . ソフトウェア プリフェッチのアドバイスは少し時代遅れです。最新のハードウェア プリフェッチャーはよりスマートであり、ハイパースレッディングは P4 の時代よりもはるかに優れています (したがって、プリフェッチ スレッドは通常無駄です)。また、タグ wiki には、そのアーキテクチャのパフォーマンス リンクが多数あります。

于 2010-10-16T02:32:55.070 に答える
31

まず第一に、メインメモリへのアクセスは非常に高価です。現在、2 GHz の CPU (最も遅いもの) は 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 を直接低下させます。そのため、利用可能なキャッシュ サイズに関しては大きなトレードオフがあります。

(現在、特定の部分を非アクティブ化して、実際の製品の欠陥がキャッシュ メモリ領域であり、全体として 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つの問題はアドレスでした。アドレスの送信には時間がかかります。メモリの大部分をアドレス指定するには、大きなアドレスを送信する必要があります。初期の頃は、アドレス バスが 1 サイクル (ティック) でアドレスを送信するのに十分な大きさではなく、アドレスを送信するのに複数のサイクルが必要であり、さらに遅延が追加されていました。

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

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

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

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

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

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

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

于 2016-08-30T10:28:57.510 に答える
24

キャッシュ ラインが 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)
于 2013-06-11T02:46:24.027 に答える
7

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

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

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

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

于 2010-10-16T01:21:04.487 に答える
4

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

于 2010-10-16T01:34:13.290 に答える