問題には、計算機能 1.3 GPU のグローバル メモリに格納されている unsigned char 配列へのストライド アクセスが含まれます。グローバル メモリの合体要件を回避するために、スレッドはグローバル メモリに順次アクセスし、次の例では 2 つのメモリ トランザクションのみを使用して配列を共有メモリにコピーします。
#include <cuda.h>
#include <stdio.h>
#include <stdlib.h>
__global__ void kernel ( unsigned char *d_text, unsigned char *d_out ) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
extern __shared__ unsigned char s_array[];
uint4 *uint4_text = ( uint4 * ) d_text;
uint4 var;
//memory transaction
var = uint4_text[0];
uchar4 c0 = *reinterpret_cast<uchar4 *>(&var.x);
uchar4 c4 = *reinterpret_cast<uchar4 *>(&var.y);
uchar4 c8 = *reinterpret_cast<uchar4 *>(&var.z);
uchar4 c12 = *reinterpret_cast<uchar4 *>(&var.w);
s_array[threadIdx.x*16 + 0] = c0.x;
s_array[threadIdx.x*16 + 1] = c0.y;
s_array[threadIdx.x*16 + 2] = c0.z;
s_array[threadIdx.x*16 + 3] = c0.w;
s_array[threadIdx.x*16 + 4] = c4.x;
s_array[threadIdx.x*16 + 5] = c4.y;
s_array[threadIdx.x*16 + 6] = c4.z;
s_array[threadIdx.x*16 + 7] = c4.w;
s_array[threadIdx.x*16 + 8] = c8.x;
s_array[threadIdx.x*16 + 9] = c8.y;
s_array[threadIdx.x*16 + 10] = c8.z;
s_array[threadIdx.x*16 + 11] = c8.w;
s_array[threadIdx.x*16 + 12] = c12.x;
s_array[threadIdx.x*16 + 13] = c12.y;
s_array[threadIdx.x*16 + 14] = c12.z;
s_array[threadIdx.x*16 + 15] = c12.w;
d_out[idx] = s_array[threadIdx.x*16];
}
int main ( void ) {
unsigned char *d_text, *d_out;
unsigned char *h_out = ( unsigned char * ) malloc ( 32 * sizeof ( unsigned char ) );
unsigned char *h_text = ( unsigned char * ) malloc ( 32 * sizeof ( unsigned char ) );
int i;
for ( i = 0; i < 32; i++ )
h_text[i] = 65 + i;
cudaMalloc ( ( void** ) &d_text, 32 * sizeof ( unsigned char ) );
cudaMalloc ( ( void** ) &d_out, 32 * sizeof ( unsigned char ) );
cudaMemcpy ( d_text, h_text, 32 * sizeof ( unsigned char ), cudaMemcpyHostToDevice );
kernel<<<1,32,16128>>>(d_text, d_out );
cudaMemcpy ( h_out, d_out, 32 * sizeof ( unsigned char ), cudaMemcpyDeviceToHost );
for ( i = 0; i < 32; i++ )
printf("%c\n", h_out[i]);
return 0;
}
問題は、データを共有メモリにコピーするときにバンクの競合が発生し (nvprof によって報告された上記の例では 384 の競合)、スレッドのシリアル化されたアクセスにつながることです。
共有メモリは、同じハーフワープの 16 のスレッドを同時に処理するために、16 (新しいデバイス アーキテクチャでは 32) の 32 ビット バンクに分割されます。データはバンク間でインターリーブされ、i 番目の 32 ビット ワードは常に i % 16 - 1 共有メモリ バンクに格納されます。
各スレッドは 1 回のメモリ トランザクションで 16 バイトを読み取るため、文字はストライド方式で共有メモリに格納されます。これにより、スレッド 0、4、8、12 の間で競合が発生します。1、5、9、13; 2、6、10、14; 同じハーフワープの 3、7、11、15。バンクの競合をなくす単純な方法は、if/else 分岐を使用して、次のようなラウンド ロビン方式でデータを共有メモリに格納することですが、深刻なスレッドの分岐が発生します。
int tid16 = threadIdx.x % 16;
if ( tid16 < 4 ) {
s_array[threadIdx.x * 16 + 0] = c0.x;
s_array[threadIdx.x * 16 + 1] = c0.y;
s_array[threadIdx.x * 16 + 2] = c0.z;
s_array[threadIdx.x * 16 + 3] = c0.w;
s_array[threadIdx.x * 16 + 4] = c4.x;
s_array[threadIdx.x * 16 + 5] = c4.y;
s_array[threadIdx.x * 16 + 6] = c4.z;
s_array[threadIdx.x * 16 + 7] = c4.w;
s_array[threadIdx.x * 16 + 8] = c8.x;
s_array[threadIdx.x * 16 + 9] = c8.y;
s_array[threadIdx.x * 16 + 10] = c8.z;
s_array[threadIdx.x * 16 + 11] = c8.w;
s_array[threadIdx.x * 16 + 12] = c12.x;
s_array[threadIdx.x * 16 + 13] = c12.y;
s_array[threadIdx.x * 16 + 14] = c12.z;
s_array[threadIdx.x * 16 + 15] = c12.w;
} else if ( tid16 < 8 ) {
s_array[threadIdx.x * 16 + 4] = c4.x;
s_array[threadIdx.x * 16 + 5] = c4.y;
s_array[threadIdx.x * 16 + 6] = c4.z;
s_array[threadIdx.x * 16 + 7] = c4.w;
s_array[threadIdx.x * 16 + 8] = c8.x;
s_array[threadIdx.x * 16 + 9] = c8.y;
s_array[threadIdx.x * 16 + 10] = c8.z;
s_array[threadIdx.x * 16 + 11] = c8.w;
s_array[threadIdx.x * 16 + 12] = c12.x;
s_array[threadIdx.x * 16 + 13] = c12.y;
s_array[threadIdx.x * 16 + 14] = c12.z;
s_array[threadIdx.x * 16 + 15] = c12.w;
s_array[threadIdx.x * 16 + 0] = c0.x;
s_array[threadIdx.x * 16 + 1] = c0.y;
s_array[threadIdx.x * 16 + 2] = c0.z;
s_array[threadIdx.x * 16 + 3] = c0.w;
} else if ( tid16 < 12 ) {
s_array[threadIdx.x * 16 + 8] = c8.x;
s_array[threadIdx.x * 16 + 9] = c8.y;
s_array[threadIdx.x * 16 + 10] = c8.z;
s_array[threadIdx.x * 16 + 11] = c8.w;
s_array[threadIdx.x * 16 + 12] = c12.x;
s_array[threadIdx.x * 16 + 13] = c12.y;
s_array[threadIdx.x * 16 + 14] = c12.z;
s_array[threadIdx.x * 16 + 15] = c12.w;
s_array[threadIdx.x * 16 + 0] = c0.x;
s_array[threadIdx.x * 16 + 1] = c0.y;
s_array[threadIdx.x * 16 + 2] = c0.z;
s_array[threadIdx.x * 16 + 3] = c0.w;
s_array[threadIdx.x * 16 + 4] = c4.x;
s_array[threadIdx.x * 16 + 5] = c4.y;
s_array[threadIdx.x * 16 + 6] = c4.z;
s_array[threadIdx.x * 16 + 7] = c4.w;
} else {
s_array[threadIdx.x * 16 + 12] = c12.x;
s_array[threadIdx.x * 16 + 13] = c12.y;
s_array[threadIdx.x * 16 + 14] = c12.z;
s_array[threadIdx.x * 16 + 15] = c12.w;
s_array[threadIdx.x * 16 + 0] = c0.x;
s_array[threadIdx.x * 16 + 1] = c0.y;
s_array[threadIdx.x * 16 + 2] = c0.z;
s_array[threadIdx.x * 16 + 3] = c0.w;
s_array[threadIdx.x * 16 + 4] = c4.x;
s_array[threadIdx.x * 16 + 5] = c4.y;
s_array[threadIdx.x * 16 + 6] = c4.z;
s_array[threadIdx.x * 16 + 7] = c4.w;
s_array[threadIdx.x * 16 + 8] = c8.x;
s_array[threadIdx.x * 16 + 9] = c8.y;
s_array[threadIdx.x * 16 + 10] = c8.z;
s_array[threadIdx.x * 16 + 11] = c8.w;
}
誰でもこれに対するより良い解決策を思いつくことができますか? SDK のリダクションの例については既に調査しましたが、それが私の問題に適用できるかどうかはわかりません。