0

最初の質問。CUDA C Programming Guide は以下のように書かれています。

L1 と共有メモリの両方に同じオンチップ メモリが使用されます。48 KB の共有メモリと 16 KB の L1 キャッシュとして、または 16 KB の共有メモリと 48 KB の L1 キャッシュとして構成できます。

ただし、デバイス クエリは「ブロックごとに使用可能なレジスタの総数: 32768」を示します。私は GTX580 を使用しています。(CC は 2.0) ガイドにはデフォルトのキャッシュ サイズが 16KB とありますが、32768 は 32768*4(バイト) = 131072 バイト = 128 K バイトを意味します。実は、どれが正しいのかわかりません。

2 番目の質問。私は以下のように設定しました、

dim3    grid(32, 32);            //blocks in a grid
dim3    block(16, 16);           //threads in a block
kernel<<<grid,block>>>(...);

すると、ブロックあたりのスレッド数は 256 になります。 => ブロックあたり 256*N 個のレジスタが必要です。N は、スレッドごとに必要なレジスターの数を意味します。(256*N)*blocks は SM あたりのレジスタ数です (バイトではありません)。したがって、デフォルト サイズが 16KB でスレッド/SM が MAX(1536) の場合、N は 2 を超えることはできません。マルチプロセッサあたりのスレッド数: 1536". 16KB/4 バイト = 4096 レジスタ、4096/1536 = 2.66666...

より大きなキャッシュ 48KB の場合、N は 8 を超えることはできません。48KB/4 バイト = 12288 レジスタ、12288/1536 = 8

本当?実際、私はとても混乱しています。


実際、私のほぼ完全なコードはここにあります。ブロックのサイズが 16x16 のときにカーネルが最適化されていると思います。ただし、8x8 の場合、16x16 などよりも高速です。理由はわかりません。

スレッドあたりのレジスタ数は 16 で、共有メモリは 80+16 バイトです。

同じ質問をしたことがありますが、正確な解決策が得られませんでした。 : CUDA Occupancy Calculator とは異なる実験の結果

#define WIDTH 512
#define HEIGHT 512
#define TILE_WIDTH 8
#define TILE_HEIGHT 8
#define CHANNELS 3
#define DEVICENUM 1 
#define HEIGHTs HEIGHT/DEVICENUM

__global__ void PRINT_POLYGON( unsigned char *IMAGEin, int *MEMin, char a, char b, char c){
        int Col = blockIdx.y*blockDim.y+ threadIdx.y;           //Col is y coordinate
        int Row = blockIdx.x*blockDim.x+ threadIdx.x;           //Row is x coordinate
        int tid_in_block = threadIdx.x + threadIdx.y*blockDim.x;
        int bid_in_grid = blockIdx.x + blockIdx.y*gridDim.x;
        int threads_per_block = blockDim.x * blockDim.y;
        int tid_in_grid = tid_in_block + threads_per_block * bid_in_grid;

        float result_a, result_b;
        __shared__ int M[15];
        for(int k = 0; k < 5; k++){
                M[k] = MEMin[a*5+k];
                M[k+5] = MEMin[b*5+k];
                M[k+10] = MEMin[c*5+k];
        }

        int result_a_up = (M[11]-M[1])*(Row-M[0]) - (M[10]-M[0])*(Col-M[1]);
        int result_b_up = (M[6] -M[1])*(M[0]-Row) - (M[5] -M[0])*(M[1]-Col);

        int result_down = (M[11]-M[1])*(M[5]-M[0]) - (M[6]-M[1])*(M[10]-M[0]);

        result_a = (float)result_a_up / (float)result_down;
        result_b = (float)result_b_up / (float)result_down;

        if((0 <= result_a && result_a <=1) && ((0 <= result_b && result_b <= 1)) && ((0 <= (result_a+result_b) && (result_a+result_b) <= 1))){
                IMAGEin[tid_in_grid*CHANNELS] += M[2] + (M[7]-M[2])*result_a + (M[12]-M[2])*result_b;      //Red Channel
                IMAGEin[tid_in_grid*CHANNELS+1] += M[3] + (M[8]-M[3])*result_a + (M[13]-M[3])*result_b;    //Green Channel
                IMAGEin[tid_in_grid*CHANNELS+2] += M[4] + (M[9]-M[4])*result_a + (M[14]-M[4])*result_b;    //Blue Channel
        }
}

struct DataStruct {
    int                 deviceID;
    unsigned char       IMAGE_SEG[WIDTH*HEIGHTs*CHANNELS];
};

void* routine( void *pvoidData ) { 
        DataStruct  *data = (DataStruct*)pvoidData;
        unsigned char *dev_IMAGE;
        int *dev_MEM;
        unsigned char *IMAGE_SEG = data->IMAGE_SEG;

        HANDLE_ERROR(cudaSetDevice(5));

        //initialize array
        memset(IMAGE_SEG, 0, WIDTH*HEIGHTs*CHANNELS);
        cudaDeviceSetCacheConfig(cudaFuncCachePreferL1);
        printf("Device %d Starting..\n", data->deviceID);

        //Evaluate Time
        cudaEvent_t start, stop;
        cudaEventCreate( &start );
        cudaEventCreate( &stop );

        cudaEventRecord(start, 0); 

        HANDLE_ERROR( cudaMalloc( (void **)&dev_MEM, sizeof(int)*35) );
        HANDLE_ERROR( cudaMalloc( (void **)&dev_IMAGE, sizeof(unsigned char)*WIDTH*HEIGHTs*CHANNELS) );

        cudaMemcpy(dev_MEM, MEM, sizeof(int)*35, cudaMemcpyHostToDevice);
        cudaMemset(dev_IMAGE, 0, sizeof(unsigned char)*WIDTH*HEIGHTs*CHANNELS);

        dim3    grid(WIDTH/TILE_WIDTH, HEIGHTs/TILE_HEIGHT);            //blocks in a grid
        dim3    block(TILE_WIDTH, TILE_HEIGHT);                         //threads in a block

        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 0, 1, 2);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 0, 2, 3);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 0, 3, 4);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 0, 4, 5);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 3, 2, 4);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 2, 6, 4);

        HANDLE_ERROR( cudaMemcpy( IMAGE_SEG, dev_IMAGE, sizeof(unsigned char)*WIDTH*HEIGHTs*CHANNELS, cudaMemcpyDeviceToHost ) );
        HANDLE_ERROR( cudaFree( dev_MEM ) );
        HANDLE_ERROR( cudaFree( dev_IMAGE ) );

        cudaEventRecord(stop, 0); 
        cudaEventSynchronize(stop);

        cudaEventElapsedTime( &elapsed_time_ms[data->deviceID], start, stop );
        cudaEventDestroy(start);
        cudaEventDestroy(stop);


        elapsed_time_ms[DEVICENUM] += elapsed_time_ms[data->deviceID];
        printf("Device %d Complete!\n", data->deviceID);

        return 0;
}
4

2 に答える 2

2

blockDim 8x8 は 16x16 より高速です。これは、ブロック サイズを大きくするとメモリ アクセスのアドレス分岐が増加するためです。

15 個の SM を搭載した GTX480 で収集されたメトリック。

metric                         8x8         16x16
duration                        161µs       114µs
issued_ipc                     1.24        1.31
executed_ipc                    .88         .59
serialization                 54.61%      28.74%

命令リプレイの数は、メモリ アクセス パターンが不適切である可能性が高いことを示しています。

achieved occupancy            88.32%      30.76%
0 warp schedulers issues       8.81%       7.98%
1 warp schedulers issues       2.36%      29.54%
2 warp schedulers issues      88.83%      52.44%

16x16 はワープ スケジューラをビジー状態に保つようです。ただし、スケジューラは命令の再発行で忙しくしています。

l1 global load trans          524,407     332,007
l1 global store trans         401,224     209,139
l1 global load trans/request    3.56        2.25
l1 global store trans/request  16.33        8.51

最優先事項は、リクエストごとのトランザクションを減らすことです。Nsight VSE ソース ビューは、命令ごとのメモリ統計を表示できます。カーネルの主な問題は、IMAGEin[] += 値のインターリーブされた U8 ロードとストアです。16x16 では、リクエストごとに 16.3 トランザクションになりますが、8x8 構成では 8.3 のみです。

IMAGEin[(i*HEIGHTs+j)*CHANNELS] += の変更 ...

連続すると、16x16 のパフォーマンスが 3 倍になります。チャネルを 4 に増やし、カーネルでパッキングを処理すると、キャッシュのパフォーマンスとメモリのスループットが向上すると思います。

リクエストごとのメモリ トランザクションの数を修正すると、実行の依存関係を調べて、ILP を増やす必要が生じる可能性があります。

于 2013-03-19T17:59:59.377 に答える
1

ブロック サイズが 8x8 の場合は、32 の倍数が小さいため高速です。下の図に示すように、32 個の CUDA コアがバインドされており、実際には同じことをスケジュールする 2 つの異なるワープ スケジューラーがあります。したがって、各実行サイクルでこれらの 32 個のコアで同じ命令が実行されます。

これをより明確にするために、最初のケース (8x8) では、各ブロックは 2 つのワープ (64 スレッド) で構成されているため、2 つの実行サイクルのみで終了しますが、(16x16) をブロック サイズとして使用している場合、それぞれに 8 時間がかかります。ワープ (256 スレッド) であるため、実行サイクルが 4 倍多くなり、コンパウンドが遅くなります。

ただし、場合によっては、SM をより多くのワープで満たす方が良い場合があります。メモリ アクセスが多く、各ワープがメモリ ストールに入る可能性がある場合 (つまり、メモリからオペランドを取得する場合)、メモリがなくなるまで別のワープに置き換えられます。操作が完了します。したがって、SM の占有率が高くなります。

もちろん、SM ごとのブロック数と SM の合計数を計算に投入する必要があります。たとえば、1 つの SM に 8 つを超えるブロックを割り当てると、その占有率が低下する可能性がありますが、おそらくあなたの場合、これらの問題に直面していません。一般に 256 は 64 よりも適切な数です。これは、SM 間でブロックのバランスを取るためです。一方、64 スレッドを使用すると、同じ SM でより多くのブロックが実行されることになります。

編集:この回答は私の推測に基づいています。より科学的なアプローチについては、Greg Smiths answer を参照してください。

レジスタープールは、共有メモリー/キャッシュとはアーキテクチャーの一番下まで異なります!

レジスタはフリップフロップで構成され、L1 キャッシュはおそらくSRAMです。

アイデアを得るために、FERMIアーキテクチャを表す下の図を見て、質問を更新して、直面している問題をさらに特定してください。

フェルミのアーキテクチャ

--ptxas-options = -v注として、オプションを nvccに渡すことで、関数が使用するレジスタと共有メモリ (smem) の数を確認できます。

于 2013-03-19T10:02:34.787 に答える