私はCUDAが初めてであり、ループの展開を理解できません。テクニックを理解するためにコードを書きました
__global__ void kernel(float *b, int size)
{
int tid = blockDim.x * blockIdx.x + threadIdx.x;
#pragma unroll
for(int i=0;i<size;i++)
b[i]=i;
}
上記は私のカーネル関数です。 main
で以下のように呼び出します
int main()
{
float * a; //Host array
float * b; //device array
int size=100;
a=(float*)malloc(size*sizeof(float));
cudaMalloc((float**)&b,size);
cudaMemcpy(b, a, size, cudaMemcpyHostToDevice);
kernel<<<1,size>>>(b,size); //size=100
cudaMemcpy(a, b, size, cudaMemcpyDeviceToHost);
for(int i=0;i<size;i++)
cout<<a[i]<<"\t";
_getch();
return 0;
}
プログラムを実行するためにsize
* size
= 10000スレッドが実行されているということですか?ループが展開されると、100個が作成されますか?
いいえ。1つのブロックでCUDAカーネルを呼び出し、その1つのブロックに100個のアクティブなスレッドがあることを意味します。サイズを2番目の関数パラメーターとしてカーネルに渡します。カーネルでは、これらの100個のスレッドのそれぞれがforループを100回実行します。
#pragma unroll
は、たとえば次のようなコードを置き換えることができるコンパイラ最適化です。
for ( int i = 0; i < 5; i++ )
b[i] = i;
と
b[0] = 0;
b[1] = 1;
b[2] = 2;
b[3] = 3;
b[4] = 4;
ループの直前に#pragma unroll
ディレクティブを配置します。展開されたバージョンの良いところは、プロセッサの処理負荷が少ないことです。 for
ループバージョンの場合、各i
をb[i]
に割り当てることに加えて、i
の初期化、6のi<5
の評価が含まれます。 i
を5回インクリメントします。 2番目のケースでは、b
配列の内容を整理するだけです(後でi
を使用する場合は、おそらくint i=5;
を追加します)。ループ展開のもう1つの利点は、命令レベルの並列処理(ILP)の強化です。展開されたバージョンでは、すべての反復でfor
ループ状態を心配することなく、プロセッサが処理パイプラインにプッシュする操作が増える可能性があります。
this のような投稿は、CUDAではランタイムループの展開が発生しないことを説明しています。あなたの場合、CUDAコンパイラにはsize
が100になる手がかりがないため、コンパイル時のループのアンロールは発生しません。したがって、アンロールを強制すると、パフォーマンスが低下する可能性があります。
size
がすべての実行で100であることが確実な場合、以下のようにループを展開できます。
#pragma unroll
for(int i=0;i<SIZE;i++) //or simply for(int i=0;i<100;i++)
b[i]=i;
SIZE
は、コンパイル時に#define SIZE 100
で認識されます。
また、コードで適切なCUDAエラーチェックを行うことをお勧めします(説明は here )。