データ・プリフェッチとは、アプリケーションによってデータが必要になる前に、メモリーよりも高速なキャッシュにデータをロードしておくことです。データ・プリフェッチの動作はアーキテクチャーによって異なります。
インテル(R) Itanium(R) プロセッサー: インテル(R) コンパイラーは通常、-O1、-O2、および -O3 (Linux*)、または /O1、/O2、および /O3 (Windows*) が指定されると、プリフェッチ命令を発行します。
インテル(R) Pentium(R) 4 プロセッサー: これらのプロセッサーは、ハードウェア・プリフェッチを行います。このため、コンパイラーは、Pentium 4 プロセッサー向けにプリフェッチ命令を発行しません。
インテル Pentium III プロセッサー: インテル・コンパイラーは、-xK (Linux) または /QxK (Windows) が指定されると、プリフェッチ命令を発行します。
ほとんどの場合、プリフェッチ命令を発行するとパフォーマンスが向上しますが、プリフェッチ命令を発行することでアプリケーションのパフォーマンスが低下する場合もあります。このような場合、プリフェッチを調整します。コンパイラー・オプションでプリフェッチをオンまたはオフにすると、他の最適化はそのままでプリフェッチによるパフォーマンス低下の原因を追求するのに役立ちます。コンパイラー・オプションを使用したデータのプリフェッチに関する詳細は、「オプションを使用したプリフェッチ」を参照してください。
プリフェッチ命令を発行するには 2 つの方法があります。1 つはコンパイラー宣言子、もう 1 つはコンパイラー組み込み関数を使用する方法です。
prefetch および noprefetch 宣言子は、インテル Itanium プロセッサーでのみサポートされています。これらの宣言子は、データ・プリフェッチがメモリー参照に生成されるか、されないかを指定します。これは、コンパイラーが使用するヒューリスティックに影響を与えます。これらのプラグマの一般的な構文は次のとおりです。
構文 |
---|
#pragma noprefetch #pragma prefetch #pragma prefetch a,b |
ループの前に prefetch A を置いて、ループ内で式 A(j) を使用する場合、コンパイラーはループ内の A(j+d) のプリフェッチを挿入します。d はデータをプリフェッチするための残りの反復回数で、コンパイラーによって決定されます。この宣言子は、-O3 (Linux) または /O3 (Windows) オプションがオンの場合にサポートされます。-O1、-O2 (Linux) または /O1、/O2 (Windows) が指定された場合もサポートされます。-O2 および /O2 は、デフォルトの最適化レベルである点に注意してください。
例 |
---|
#pragma noprefetch b #pragma prefetch a for(i=0; i<m; i++) { a[i]=b[i]+1; } |
次の例では、prefetch、noprefetch、および memref_control プラグマを一緒に使用する方法を説明します。
例 |
---|
#define SIZE 10000 int prefetch(int *a, int *b) { int i, sum = 0; #pragma memref_control a:l2 #pragma noprefetch a #pragma prefetch b for (i = 0; i<SIZE; i++) sum += a[i] * b[i]; return sum; } #include <stdio.h> int main() { int i, arr1[SIZE], arr2[SIZE]; for (i = 0; i<SIZE; i++) { arr1[i] = i; arr2[i] = i; } printf("Demonstrating the use of prefetch, noprefetch,\n" "and memref_control pragma together.\n"); prefetch(arr1, arr2); return 0; } |
memref_control プラグマは、インテル Itanium プロセッサーでのみサポートされています。このプラグマは、ロード・レイテンシーの制御方法とさまざまなレベルの一時的な空間の制御方法を提供します。memref_control プラグマは、配列レベルで局所性およびレイテンシーを指定できるようにします。例えば、プラグマを使用することで次の制御が可能になります。
将来のアクセス用にデータを格納する場所 (キャッシュレベル)。
ロードに使用されるのに最適なレイテンシー値、またはこの参照のプリフェッチが発行された場合にオーバーラップされなければならないレイテンシー。
このプラグマの構文は次のとおりです。
構文 |
---|
#pragma memref_control [name1[:<locality>[:<latency>]],[name2...] |
次の表は、サポートされる引数のリストです。
引数 |
説明 |
---|---|
name1、name2 |
配列またはポインターの名前。少なくとも 1 つの name を指定する必要がありますが、関連付けられた locality および latency の値を指定することができます。 |
locality |
将来のアクセス用にデータを格納するキャッシュレベルを示す整数値 (オプション)。これにより、この参照で使用されるロード/ストアのヒント (またはプリフェッチのヒント) が決定します。次のいずれかの値を使用できます。
この引数を使用するには、name も指定してください。 |
latency |
ロードを示す整数値 (またはこのアドレスに対してプリフェッチが発行された場合にオーバーラップしなければいけないレイテンシー) (オプション)。次のいずれかの値を使用できます。
この引数を使用するには、name と locality も指定しなければなりません。 |
特定のデータアクセスに対して、高いレベルでソースレベルのデータの局所性情報を指定した場合、この情報の使用方法はコンパイラーが決定します。コンパイラーは、参照に有利にプリフェッチした場合、指定されたレイテンシーをカバーする距離のプリフェッチを発行し、対応するロードを指定されたレイテンシーよりも小さなレイテンシーでスケジュールします。また、プリフェッチのヒントを使用して、指定されたキャッシュレベルにデータが保たれるよう適切にロードします。
コンパイラーは、事前にアドレスを計算できない場合、またはプリフェッチのオーバーヘッドが大きすぎると判断した場合、指定されたレイテンシーを使用して (パイプライン化されたループまたはグローバル・コード・スケジューラーによるループで) ロードとその使用を分離します。ロード/ストアのヒントは、locality 引数とともに渡されたキャッシュレベルに対応します。
これを prefetch および noprefetch とともに使用して、ヒントとプリフェッチ手法をチューニングすることが可能です。memref_control を noprefetch とともに使用する場合は、次のガイドラインに従ってください。
noprefetch を memref_control とともに指定すると、コンパイラーはプリフェッチを発行しません。代わりに、memref_control で指定されたレイテンシーの値を使用してロードがスケジュールされます。
2 つのプラグマを一緒に使用する際、決められた順序はありません。プラグマを適用するループの直前に、連続して (どちらが先でも良い) 指定します。1 つのヒントとともにプリフェッチを発行し、後で別のヒントを使用してロードすると、特定のアーキテクチャーに使用されるヒントをより良く制御できます。
memref_control は、prefetch または noprefetch とは処理が異なります。ロードがプリフェッチされない場合でも、非デフォルトのロード・レイテンシーを latency 引数に渡すことによって参照をロードすることができます。
次の例は、事前にアドレスが不明でプリフェッチできないケースを示します。この例では、コンパイラーは (ソフトウェアのパイプライン化が行われたループ、またはグローバル・コード・スケジューラーによるループにおける) L3 ロード・レイテンシーが 15 サイクルのタブ配列のロードをスケジュールします。
例: gather |
---|
#pragma memref_control tab : l2 : l3_latency for (i=0; i<n; i++) { x = <generate 64 random bits inline>; dum += tab[x&mask]; x>>=6; dum += tab[x&mask]; x>>=6; dum += tab[x&mask]; x>>=6; } |
次の例では、memref_control、prefetch、および noprefetch を一緒に使用する 1 つの方法を示します。
例: sparse matrix |
---|
if( size <= 1000 ) { #pragma noprefetch cp, vp #pragma memref_control x:l2:l3_latency
#pragma noprefetch yp, bp, rp #pragma noprefetch xp for (iii=0; iii<rag1m0; iii++) { if( ip < rag2 ) { sum -= vp[ip]*x[cp[ip]]; ip++; } else { xp[i] = sum*yp[i]; i++; sum = bp[i]; rag2 = rp[i+1]; } } xp[i] = sum*yp[i]; } else {
#pragma prefetch cp, vp #pragma memref_control x:l2:mem_latency
#pragma prefetch yp, bp, rp #pragma noprefetch xp for (iii=0; iii<rag1m0; iii++) { if( ip < rag2 ) { sum -= vp[ip]*x[cp[ip]]; ip++; } else { xp[i] = sum*yp[i]; i++; sum = bp[i]; rag2 = rp[i+1]; } } xp[i] = sum*yp[i]; } |
コンパイラー組み込み関数を挿入する前に、サポートされる他のコンパイラー・オプションとプラグマを試してみてください。コンパイラー組み込み関数は、コンパイラー・オプションやコンパイラー・プラグマよりも、移植性および柔軟性が低くなります。
プラグマは、コンパイラーの最適化を有効にしますが、組み込み関数は最適化を実行します。プラグマを使用したプログラムは移植性が高いため、コンパイラーは別のプロセッサーに適用することができますが、組み込み関数を使用したプログラムを別のプロセッサーに適用するには、書き換えや移植が必要です。これは、組み込み関数がアセンブリー言語のプログラミングに近いためです。
一部のプリフェッチ組み込み関数を次に示します。
組み込み関数 |
説明 |
---|---|
__lfetch |
lfetch.lfhint 命令を生成します。 |
__lfetch_fault |
lfetch.fault.lfhint 命令を生成します。 |
__lfetch_excl |
lfetch.excl.lfhint 命令を生成します。 |
__lfetch_fault_excl |
lfetch.fault.excl.lfhint 命令を生成します。 |
__mm_prefetch |
1 キャッシュライン分のデータを、アドレス a からプロセッサーに近い位置にロードします。 |
これらの組み込み関数に関する詳細は、「コンパイラー・リファレンス」の「オペレーティング・システムに関連する組み込み関数」および「ストリーミング SIMD 拡張命令によるキャッシュ制御」を参照してください。
次の例では、プリフェッチ組み込み関数を使用して lfetch.nt2 命令を生成する方法を説明します。
例 |
---|
for (i=i0; i!=i1; i+=is) { float sum = b[i]; int ip = srow[i]; int c = col[ip]; for(; ip<srow[i+1]; c=col[++ip]) lfetch(2, &value[ip+40]); _// mm_prefetch(&value[ip+40], 2); sum -= value[ip] * x[c]; y[i] = sum; } |
SSE がサポートされているプロセッサーでは、次の SSE 組み込み関数も使用できます。
_mm_prefetch
_mm_stream_pi
_mm_stream_ps
_mm_sfence
『インテル(R) Itanium(R) アーキテクチャー・ソフトウェア・デベロッパーズ・マニュアル、第 3 巻: 命令セット・リファレンス、Revision 2.1 第 1 部 インテル(R) Itanium(R) 命令セットの説明』(英語) (http://www.intel.com/design/itanium/manuals/245319.pdf) を参照してください。