グローバルメモリからの M*N フロートが必要なデバイスで同時に実行する「N」個のスレッドがあります。合体したグローバルメモリにアクセスする正しい方法は何ですか? この問題で、共有メモリはどのように役立ちますか?
1 に答える
通常、隣接するスレッドがメモリ内の隣接するセルにアクセスすると、適切な合体アクセスを実現できます。したがって、tid
スレッドのインデックスを保持している場合は、次の場所にアクセスします。
arr[tid]
---完璧な合体を与えますarr[tid+5]
---ほぼ完璧で、おそらくずれていますarr[tid*4]
---ギャップがあるため、もうそれほど良くありませんarr[random(0..N)]
- - 最悪!
私はCUDAプログラマーの観点から話していますが、他の場所でも同様のルールが適用されます。単純なCPUプログラミングでも、影響はそれほど大きくありません。
「しかし、私は非常に多くの配列を持っているので、誰もが私のスレッドの数の約2〜3倍長く、「arr [tid *4]」のようなパターンを使用することは避けられません。これの治療法は何でしょうか?」
オフセットがより高い2乗(たとえば16*xまたは32*x)の倍数である場合、問題はありません。したがって、forループでかなり長い配列を処理する必要がある場合は、次のようにすることができます。
for (size_t base=0; i<arraySize; i+=numberOfThreads)
process(arr[base+threadIndex])
(上記は、配列サイズがスレッド数の倍数であることを前提としています)
したがって、スレッド数が32の倍数であれば、メモリアクセスは良好です。
もう一度注意してください:私はCUDAプログラマーの観点から話しています。さまざまなGPU/環境では、完全なメモリアクセスの合体のために必要なスレッドの数は増減する可能性がありますが、同様のルールを適用する必要があります。
「32」は、グローバルメモリと並行してアクセスするワープサイズに関連していますか?
直接ではありませんが、いくつかのつながりがあります。グローバルメモリは、ハーフワープによってアクセスされる32、64、および128バイトのセグメントに分割されます。特定のメモリフェッチ命令でアクセスするセグメントが多いほど、その命令は長くなります。詳細については、「CUDAプログラミングガイド」を参照してください。このトピックに関する章全体があります:「5.3。メモリスループットの最大化」。
さらに、メモリアクセスをローカライズするための共有メモリについて少し聞いた。これはメモリの合体に適していますか、それとも独自の問題がありますか?
共有メモリはオンチップであるためはるかに高速ですが、サイズには制限があります。メモリはグローバルのようにセグメント化されていないため、ペナルティコストなしでほぼランダムにアクセスできます。ただし、幅4バイト(32ビット整数のサイズ)のメモリバンクラインがあります。各スレッドがアクセスするメモリのアドレスは、16を法として異なる必要があります(GPUによっては32)。したがって、アドレス[tid*4]
は、よりもはるかに遅くなります[tid*5]
。これは、最初の1つがバンク0、4、8、12にのみアクセスし、後者が0、5、10、15、4、9、14、...にアクセスするためです(バンクID=アドレスモジュロ16 )。
繰り返しになりますが、CUDAプログラミングガイドで詳細を読むことができます。