web-dev-qa-db-ja.com

#pragma unrollは正確に何をしますか?スレッドの数に影響しますか?

私は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個が作成されますか?

24

いいえ。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ループバージョンの場合、各ib[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 )。

32
Farzad