まず、これは静的配列 (例: ) または動的に割り当てられた配列 (例:およびそれに続くs)tab
などの大きな 2D 配列であると想定します。ここでは、実行時間を短縮するために、一部のデータをキャッシュからプリフェッチします。int tab[1024*1024][1024*1024]
int** tab
malloc
tab
簡単に言えば、2D 配列の単純なリダクションが実行されるコードにプリフェッチを手動で挿入する必要はないと思います。最新の CPU は、必要に応じて利益があれば、自動プリフェッチを行います。
この問題について知っておくべき 2 つの事実:
tab
(1)最内ループの内側の空間的局所性をすでに利用しています。tab[i][0]
(キャッシュ ミスまたはページ フォールトの後) が読み取られると、キャッシュ ライン サイズが 64 バイトであると仮定して、 から のデータがCPUキャッシュに格納されますtab[i][0]
。tab[i][15]
(2) ただし、コードが行内をトラバースする場合、つまり に移動する場合tab[i][M-1]
、tab[i+1][0]
コールド キャッシュ ミスが発生する可能性が高くなります。特にtab
が動的に割り当てられた配列で、各行が断片化された方法で割り当てられる可能性がある場合はそうです。ただし、配列が静的に割り当てられている場合、各行はメモリ内で連続して配置されます。
したがって、プリフェッチは、(1) 次の行の最初の項目を読み取り、(2)j + CACHE_LINE_SIZE/sizeof(tab[0][0])
事前に読み取る場合にのみ意味があります。
__builtin_prefetch
上部ループにプリフェッチ操作 (例: ) を挿入することで、これを行うことができます。ただし、最新のコンパイラは、常にそのようなプリフェッチ命令を発行するとは限りません。本当にそれを行いたい場合は、生成されたバイナリ コードを確認する必要があります。
ただし、先に述べたように、最新の CPU はほとんどの場合プリフェッチを自動的に行い、自動プリフェッチは手動コードよりもパフォーマンスが優れているため、これを行うことはお勧めしません。たとえば、Ivy Bridge プロセッサのような Intel CPU には、L1、L2、または L3 キャッシュへのプリフェッチなど、複数のデータ プリフェッチャーがあります。(ただし、モバイル プロセッサには派手なデータ プリフェッチャーはないと思います)。一部のプリフェッチャーは、隣接するキャッシュ ラインをロードします。
大規模な 2D 配列でより高価な計算を行う場合は、キャッシュに適した代替アルゴリズムが多数あります。注目すべき例は、ブロックされた(タイトルの付いた)行列乗算です。単純な行列乗算では多くのキャッシュ ミスが発生しますが、ブロックされたアルゴリズムでは、キャッシュに適合する小さなサブセットで計算することにより、キャッシュ ミスが大幅に減少します。このような参考文献を参照してください。