web-dev-qa-db-ja.com

CUDAコンスタントメモリのベストプラクティス

ここにいくつかのコードを示します

__constant__ int array[1024];

__global__ void kernel1(int *d_dst) {
   int tId = threadIdx.x + blockIdx.x * blockDim.x;
   d_dst[tId] = array[tId];
}

__global__ void kernel2(int *d_dst, int *d_src) {
   int tId = threadIdx.x + blockIdx.x * blockDim.x;
   d_dst[tId] = d_src[tId];
}

int main(int argc, char **argv) {
   int *d_array;
   int *d_src;
   cudaMalloc((void**)&d_array, sizeof(int) * 1024);
   cudaMalloc((void**)&d_src, sizeof(int) * 1024);

   int *test = new int[1024];
   memset(test, 0, sizeof(int) * 1024);

   for (int i = 0; i < 1024; i++) {
     test[i] = 100;
   }

   cudaMemcpyToSymbol(array, test, sizeof(int) * 1024);
   kernel1<<< 1, 1024 >>>(d_array);

   cudaMemcpy(d_src, test, sizeof(int) * 1024, cudaMemcpyHostToDevice);
   kernel2<<<1, 32 >>>(d_array, d_src),

   free(test);
   cudaFree(d_array);
   cudaFree(d_src);

   return 0;
}

これは単に一定のメモリとグローバルメモリの使用量を示しています。その実行時に、「kernel2」は「kernel1」よりも(時間の観点から)約4倍速く実行されます。

これは、Cuda Cプログラミングガイドから、定数メモリへのアクセスがシリアル化されているためだと理解しています。これは、ワープが整数、浮動小数点数、倍精度などの単一の定数値にアクセスする場合に定数メモリを最適に利用できるが、配列にアクセスすることはまったく有益ではないという考えに私を導きます。言い換えると、一定のメモリアクセスから有益な最適化/高速化の利益を得るには、ワープが単一のアドレスにアクセスする必要があると言えます。これは正しいです?

また、定数メモリに単純型ではなく構造体を保持しているかどうかも知りたいです。縦糸のある糸による構造へのアクセス。シングルメモリアクセス以上と見なされますか?たとえば、構造体に複数の単純な型と配列が含まれる場合があります。これらの単純なタイプにアクセスするとき、これらのアクセスもシリアル化されていますか?

最後の質問は、定数値の配列がある場合です。この配列には、ワープ内のさまざまなスレッドを介してアクセスする必要があります。アクセスを高速化するには、一定のメモリではなくグローバルメモリに保持する必要があります。あれは正しいですか?

効率的な一定のメモリ使用量が示されているサンプルコードを誰でも参照できます。

よろしく、

19
Psypher

一定のメモリアクセスから有益な最適化/高速化の利益を得るには、ワープが単一のアドレスにアクセスする必要があると言えます。これは正しいです?

はい、これは一般的に正しく、一定のメモリ/一定のキャッシュを使用する主な目的です。コンスタントキャッシュは、SMごとにサイクルごとに1つの32ビット量を提供できます。したがって、ワープ内の各スレッドが同じ値にアクセスしている場合:

int i = array[20];

そうすれば、一定のキャッシュ/メモリから大きな利益を得る機会が得られます。ワープの各スレッドが一意の量にアクセスしている場合:

int i = array[threadIdx.x]; 

その後、アクセスはシリアル化され、一定のデータ使用量はパフォーマンスの面で期待外れになります。

また、定数メモリに単純型ではなく構造体を保持しているかどうかも知りたいです。縦糸のある糸による構造へのアクセス。シングルメモリアクセス以上と見なされますか?

あなたは確かに一定のメモリに構造を置くことができます。同じルールが適用されます。

int i = constant_struct_ptr->array[20]; 

恩恵を受ける機会がありますが

int i = constant_struct_ptr->array[threadIdx.x];

ではない。スレッド間でsame単純型構造要素にアクセスする場合、これは一定のキャッシュ使用に理想的です。

最後の質問は、定数値の配列がある場合です。この配列には、ワープ内のさまざまなスレッドを介してアクセスする必要があります。アクセスを高速化するには、一定のメモリではなくグローバルメモリに保持する必要があります。あれは正しいですか?

はい。一般に、アクセスによって一定のメモリが1サイクルあたり32ビットの量で破壊されることがわかっている場合は、データを通常のグローバルメモリに残したほうがよいでしょう。

__constant__データの使用法を示すさまざまな cudaサンプルコード があります。ここにいくつかあります:

  1. グラフィックボリュームレンダー
  2. イメージングbilateralFilter
  3. イメージングconvolutionTexture
  4. ファイナンスMonteCarloGP

そして他にもあります。

EDIT:一定のメモリにこのような構造がある場合、コメントの質問に応答します。

struct Simple { int a, int b, int c} s;

そして、次のようにアクセスします。

int p = s.a + s.b + s.c;
          ^     ^     ^
          |     |     |
cycle:    1     2     3

定数メモリ/キャッシュを適切に使用します。 Cコードがコンパイルされると、内部で、上の図の1,2,3に対応するマシンコードアクセスが生成されます。アクセス1が最初に発生すると想像してみましょう。アクセス1はワープ内のどのスレッドに関係なく同じメモリ位置にアクセスするため、サイクル1の間、すべてのスレッドはs.aの値を受け取り、それを利用します。可能な限り最高の利益のためのキャッシュの。アクセス2と3についても同様です。一方、次の場合:

struct Simple { int a[32], int b[32], int c[32]} s;
...
int idx = threadIdx.x + blockDim.x * blockIdx.x;
int p = s.a[idx] + s.b[idx] + s.c[idx];

これでは、一定のメモリ/キャッシュを適切に使用できません。代わりに、これがsへのアクセスの典型である場合、通常のグローバルメモリでsを見つけるパフォーマンスが向上する可能性があります。

24
Robert Crovella