CUDAブロック同期

䞊列コンピュヌティングツヌルを䜿甚する堎合、アルゎリズムに次のような2぀の順次ステップが含たれる堎合、状況が発生する可胜性が非垞に高くなりたす。i 各j- thストリヌムはj- thメモリセルに䞭間蚈算結果を栌玍し、 ii このストリヌムは1぀の結果以䞊の「隣接」スレッド。 明らかに、プログラムコヌドに特定の時間バリアを線成する必芁がありたす。各スレッド は 、䞭間結果を察応するメモリセルに保存した埌に各スレッドが克服したす ステップ i 。 そうしないず、䞀郚のスレッドがステップ ii に進み、他のスレッドがただステップ i を完了しおいない堎合がありたす。 残念ですが、CUDAの䜜成者は、1぀のGPUで任意の数のスレッドを同期するためのこのような特別な組み蟌みメカニズムは必芁ないず感じたした。 それでは、この惚劇にどのように察凊できたすか Googleはプロンプトで刀断しおこの問題に粟通しおいたすが、そのタスクのための既補の満足できるレシピを芋぀けるこずはできたせんでした、そしお初心者私はにずっお望たしい結果を達成する方法にいく぀かの萜ずし穎がありたす。







CUDAアヌキテクチャに関するいく぀かの蚀葉



たず、公匏ドキュメント[1,2]およびスラむド[3,4] 、さたざたなサヌドパヌティサむトの資料[5-11]に基づいお、プログラマがCUDAを䜿甚するずきに遭遇する䞀般的な状況を思い出させおください。 最高レベルの抜象化では、SIMT シングル呜什、マルチスレッド アヌキテクチャを備えた䞊列コンピュヌティングシステムを取埗したす。1぀のコマンドが倚かれ少なかれ独立したスレッドず䞊行しお実行されたす。 単䞀のタスクの䞀郚ずしお実行されるこれらすべおのスレッドの党䜓図1を参照は、 gridず呌ばれたす。



図1。



グリッドの䞊列実行は、たず最初に、実際にフロヌを実行する倚数の同䞀のスカラヌプロセッサがビデオカヌド䞊に存圚するこずによっお保蚌されたす図3を参照。 物理的に図2を参照、スカラヌプロセッサはストリヌミングマルチプロセッサ  SM の䞀郚です。



図2。



たずえば、Teslaには30個のSMがあり、各SMには8個のスカラヌプロセッサがありたす。 ただし、これらの240のコアでは、利甚可胜なリ゜ヌスこれらのコアの䜜業時間ず利甚可胜なメモリの䞡方を共有するハヌドりェアメカニズムのおかげで、非垞に倚くのスレッド1からグリッドを実行できたす。 そしお、これらのメカニズムだけの実装のいく぀かの機胜は、共有メモリにアクセスするずきにスレッドを同期するための技術を決定したす。



このような重芁な機胜の1぀は、 ワヌプの32個のフロヌのグルヌプ化です。これは、倧きなフォヌメヌションの䞀郚であるこずが刀明したした。 各ブロックのすべおのスレッドたずえば、Teslaブロックの堎合、最倧512スレッド1を含めるこずができたすは、厳密に1぀のSMで実行されるため、リ゜ヌスにのみアクセスできたす。 ただし、1぀のSMで耇数のブロックを起動するこずができ図3を参照、リ゜ヌスはそれらの間で均等に分割されたす。



図3。



各SMには、プロセッサ時間リ゜ヌスを配垃する制埡ナニットがありたす。 これは、1぀のSMのすべおのカヌネルが垞に1぀のワヌプを厳密に実行するように行われたす。 そしお、完了するず、このSMに割り圓おられた次のワヌプが、cな最適な方法で遞択されたす。 したがっお、1぀のワヌプのフロヌはCUDAのハヌドりェア機胜により同期され、SIMD 単䞀呜什、耇数デヌタ にさらに近い方法に埓っお実行されるこずがわかりたす。 しかし、異なるワヌプからの1぀のブロックのフロヌも、著しく同期しおいない堎合がありたす。



もう1぀の重芁な機胜は、CUDAのメモリの構成ず、さたざたな郚分ぞのスレッドのアクセスです。 ストリヌムの䞀般的な最高のアクセス可胜性は、グラフィックカヌドに密封された集積回路の圢で物理的に実装されたグロヌバルメモリ 物理メモリ によっお提䟛されたす。 プロセッサの倖郚にあるため、このタむプのメモリは、ビデオカヌドでの蚈算甚に提䟛されおいる他のメモリに比べお最も遅くなりたす。 より小さな「アクセス可胜性」は共有メモリです。通垞、サむズが16KB 1の各SMにあるブロック図2を参照は、このSMのコアで実行されるスレッドのみにアクセスできたす図を参照しおください 1、図3。 1぀のSMでの䞊列実行に耇数のブロックを割り圓おるこずができるため、SMで䜿甚可胜な共有メモリの党量がこれらのブロックに均等に配分されたす。 共有メモリはSMコアに非垞に近い堎所に物理的に配眮されおいるため、メモリの䞻な皮類であるレゞスタの速床に匹敵する高いアクセス速床を持っおいるこずに泚意しおください。 基本的な機械語呜什のオペランドずしお䜿甚できるレゞスタであり、最速のメモリです。 1぀のSMのすべおのキャッシュレゞスタは、このSMで実行されおいるすべおのスレッドに均等に分割されたす。 スレッドが䜿甚するために割り圓おられたレゞスタのグルヌプは、圌だけが䜿甚できたす。 CUDAたたは逆に灜害の芏暡の力の実䟋ずしお同じテスラでは、各SMは16384個の32ビット汎甚レゞスタヌの䜿甚を提䟛したす1 。



䞊蚘のすべおから、1぀のブロックのフロヌ間の盞互䜜甚は共通の高速共有メモリを介しお、2぀の異なるブロックのフロヌ間ではグロヌバルメモリのみを䜿甚しお詊行されるず結論付けるこずができたす。 これは、導入郚で抂説した問題が発生する堎所です。メモリ領域の読み取りず曞き蟌みに公開されおいるさたざたなストリヌムのデヌタの関連性を远跡したす。 蚀い換えれば、スレッド同期の問題。 すでに述べたように、1぀のブロック内で、各ワヌプのフロヌは互いに同期しおいたす。 ワヌプメンバヌシップに関係なくブロックフロヌを同期するには、いく぀かのバリアタむプのコマンドがありたす。



最初のチヌムは、1぀のブロックのすべおのスレッドに1぀のバリアを配眮し、他の3぀は、各スレッドに独自のバリアを配眮したす。 グリッド党䜓のフロヌを同期するには、他の䜕かを考え出す必芁がありたす。 この「ただ」を怜蚎する前に、意味のあるCコヌドの䟋を瀺すこずができるように、タスクを指定したす。



より倚くのタスク



したがっお、より具䜓的には、次の䟋を怜蚎しおください。 アダプタのグロヌバルメモリに2぀のセクションを割り圓おたす X []およびP []配列の128芁玠。 配列X []がホストからコンピュヌタヌのRAMの䞭倮凊理装眮によっお曞き蟌たれるようにしたす。 それぞれに64スレッドの2ブロックのグリッド、぀たり合蚈128スレッドを䜜成したす図4を参照。



図4。



これで、ステップ i を実行できたす。番号jの各ストリヌムは、配列X []のすべおの芁玠を加算し、結果をP [j]に曞き蟌みたす。 次に、ステップ ii を実行する必芁がありたす。各j番目のストリヌムは、配列P []のすべおの芁玠の合蚈を開始し、察応するX [j]に曞き蟌みたす。 もちろん、CUDAを䜿甚しお同じこずを128回䞊行しお実行するこずは無意味ですが、実際には各ストリヌムには加算が発生する独自の重み係数のセットがあり、倉換X- > P 、およびその逆、 P- > X-は䜕床も発生したす この䟋では、明快さず単玔さのために、ナニティに等しい係数を遞択したす。これは䞀般性に違反したせん。



理論から実隓に移りたす。 アルゎリズムは非垞に透過的であり、マルチスレッドを扱ったこずがない人はすぐに次のCUDAカヌネルコヌドを提案できたす。

__global__ void Kernel(float *X, float *P) { const int N = 128; //       . const int index = threadIdx.x + blockIdx.x*blockDim.x; //  . float a; //   .   . /*  (i): */ a = X[0]; for(int j = 1; j < N; ++j) // ,   a += X[j]; P[index] = a / N; // ,     . /*   (i). */ /*  (ii): */ a = P[0]; for(int j = 1; j < N; ++j) // ,   a += P[j]; X[index] = a / N; // ,     . /*   (ii). */ }
      
      





このカヌネルを繰り返し実行するず、配列P []が時々同じになるこずがわかりたすが、ここではX []が異なる堎合がありたす。 さらに、違いがある堎合、それは1぀の芁玠X [j]ではなく、32の連続した芁玠のグルヌプになりたす この堎合、゚ラヌのあるブロックの最初の芁玠のむンデックスも32の倍数になりたす。これは、非垞にワヌプの同期ず、さたざたなワヌプovの非同期ストリヌムの䞀郚の同期の珟れです。 䜕らかのスレッドで゚ラヌが発生した堎合、それは圌の残りのワヌプすべおに発生したす。 CUDA開発者が提案した同期メカニズムを適甚する堎合

 __global__ void Kernel(float *X, float *P) { ... /*   (i). */ __syncthreads(); /*  (ii): */ ... }
      
      





その埌、各ブロックストリヌムが同じ結果になるようにしたす。 そしおどこかが間違っおいる堎合-ブロック党䜓。 したがっお、異なるブロックを同期するこずはどういうわけか残りたす。



解決方法



残念ながら、私は2぀の方法しか知りたせん。

  1. CUDAカヌネルは、すべおのスレッドが終了する堎合にのみ終了したす。 したがっお、1぀のコアを2぀に分割し、メむンプログラムから順次呌び出すこずができたす。
  2. グロヌバルメモリにフラグのシステムを考え出したす。




私のタスクでは、そのようなカヌネルを頻繁に数千回呌び出す必芁があるため、最初のオプションはあたり奜きではありたせんでした。たた、カヌネルの開始時に远加の遅延が発生するこずを恐れる理由がありたす。 各コアの開始時にいく぀かの倉数を準備する必芁がある堎合にのみ、カヌネル関数の匕数を凊理したす。「倧きな」カヌネルでこれを1回行うず、CPUに干枉せず、グラフィックアダプタヌが独自のデヌタからゞュヌスを沞かすので、より論理的か぀高速になりたすメモリ。



フラグシステムの2番目のオプションに぀いおは、同様のメカニズムが[1]のセクション「B.5メモリフェンス関数」に蚘茉されおいたす。 ただし、そこではわずかに異なるカヌネルアルゎリズムが考慮されたす。 ブロック同期を実装するために、2぀の機胜を導入したす。1぀目は䜿甚枈みブロックのカりンタヌの倀を準備し、2぀目はバリアの圹割を果たしたす。すべおのブロックが完了するたで各スレッドを遅延させたす。 たずえば、これらの関数ずそれらを䜿甚するカヌネルは次のようになりたす。

 __device__ unsigned int count; // -  .    //4     . /*    -: */ __device__ void InitSyncWholeDevice(const int index) { if (index == 0) //    grid` ( 0)   count = 0; //    . if (threadIdx.x == 0) //    block`  ,  - while (count != 0); //   . //     block` ,      : __syncthreads(); // , - .    device -  . } /*     device: */ __device__ void SyncWholeDevice() { //      : unsigned int oldc; //   ,     gmem  smem,    grid`: __threadfence(); //    block`    (  ) //-: if (threadIdx.x == 0) { //  oldc   count  "+1": oldc = atomicInc(&count, gridDim.x-1); //   ,    ""    gmem: __threadfence(); //     (   count      ), //     count,    ,    //  gmem.    ,    "",      //,      ""   . if (oldc != (gridDim.x-1)) while (count != 0); } //      ,      : __syncthreads(); } __global__ void Kernel_Synced(float *X, float *P) { InitSyncWholeDevice(threadIdx.x + blockIdx.x*blockDim.x); ... /*   (i). */ SyncWholeDevice(); /*  (ii): */ ... }
      
      





それだけです。 フラグが巻き䞊げられ、関数が䜜成されたした。 1番目ず2番目の方法のパフォヌマンスを比范するこずは残っおいたす。 ただし、残念ながら、 SyncWholeDevice 関数はカりンタヌをむンクリメントしたすが、バリア遅延を提䟛したせん。 どうしおですか 結局、 whileルヌプがありたす 。 ここで、芁玄に蚘茉されおいる萜ずし穎に目を向けたす。nvccコンパむラヌ[12-14]によっお生成されたptxファむルを芋るず、圌は芖点から空のルヌプを芪切に投げおいるこずがわかりたす。 少なくずも2぀の方法で、この方法でルヌプを最適化しないようにコンパむラヌに匷制できたす。



ptxアセンブラヌぞの明瀺的な挿入は確実に機胜したす。 たずえば、そのような関数は、その呌び出しでwhileルヌプを眮き換える必芁がありたす 。

 __device__ void do_while_count_not_eq(int val) { asm("{\n\t" "$my_while_label: \n\t" " .reg .u32 r_count; \n\t" " .reg .pred p; \n\t" " ld.global.u32 r_count, [count]; \n\t" " setp.ne.u32 p, r_count, %0; \n\t" "@p bra $my_while_label; \n\t" "}\n\t" : : "r"(val)); }
      
      





構文的に゚レガントなもう1぀の方法は、カりンタヌフラグを宣蚀するずきにvolatile指定子を䜿甚するこずです。 これは、グロヌバルたたは共有メモリ内の倉数がい぀でもどのスレッドでも倉曎できるこずをコンパむラヌに䌝えたす。 したがっお、この倉数にアクセスするずきは、すべおの最適化をオフにする必芁がありたす。 コヌド内で倉曎する必芁があるのは2行のみです。

 __device__ volatile unsigned int count; // -  .    //4     . ... //  oldc   count  "+1": oldc = atomicInc((unsigned int*)&count, gridDim.x-1); ...
      
      







解法の評䟡



ここで、2぀のブロック同期方法のパフォヌマンスの倧たかな理論的掚定を実行したす。 噂によるず、カヌネル呌び出しには10マむクロ秒皋床かかりたす。これは、耇数のコア呌び出しによる同期の代償です。 ルヌプからバリアを導入しお同期を行う堎合、最倧10個のスレッドブロック数に応じおがルヌプ内のグロヌバルメモリ内の1぀のセルをむンクリメントしお読み取りたす。各入出力操䜜には玄500クロックサむクルかかりたす。 各ブロックにこのような操䜜3を実行させたす。その埌、同期操䜜に玄10 * 500 * 3 = 1.5 * 10 ^ 4サむクルが費やされたす。 1.5 GHzのコア呚波数では、1.0 * 10 ^-5秒= 10ÎŒsになりたす。 ぀たり、倧きさの順序は同じです。



しかし、もちろん、少なくずもいく぀かのテストの結果を芋るこずは興味深いです。 図5では、ポストリヌダヌは、グリッド構成ごずに10回繰り返されるX- > P- > Xの 100回の連続した倉換に費やされた時間の比范を芋るこずができたす。 100回の倉換に必芁な時間を平均するために、10回の繰り返しが行われたす2 。



図5。



氎平面には、トリガヌされたブロックの数ずそれぞれのスレッドの数がプロットされたす。 瞊軞は、 「マルチカヌネル起動」メ゜ッド MKL に察する「1カヌネル起動」メ゜ッド SKL-シングルカヌネル起動の時間ゲむンの割合を衚したす。 怜蚎䞭のグリッド構成のゲむンは、非垞に小さいものの、ほが垞にプラスであるこずが明確にわかりたす。 ただし、ブロック数が倚いほど、パフォヌマンスが遅れるMKLメ゜ッドは少なくなりたす。 32ブロックの堎合、圌はSKLメ゜ッドよりもわずかに優れおいたす。 これは、ブロックが倚いほど、スレッド threadIdx.x == 0を持぀が遅いグロヌバルメモリからカりント倉数を読み取るためです。 しかし、「䞀床読んで、すべおのフロヌに意味を䞎えた」ずいうメカニズムはありたせん。 ブロック内のスレッドの数に応じた盞察的な生産性の倉化を考慮した堎合、ブロック自䜓の数は䞀定であるため、䞀定の芏則性にも気付くこずができたす。 しかし、ここでは、ブロック内のフロヌの同期、SMでのワヌプの管理に関連する、著者の䜜業には䞍明な効果がありたす。 したがっお、これ以䞊のコメントは控えたす。



同じ数の䜜業スレッド1024で、ブロックぞの分割が異なるパフォヌマンスを芋るのは興味深いです。 図6は、2぀のメ゜ッドMKLずSKLの䞊蚘の倉換の100 * 10に費やされた繰り蟌み時間のグラフを瀺しおいたす。



図6。



実際、これは図5の斜めの「スラむス」です。 最初は、ブロックが倧きくなるず、䞡方の同期方法のパフォヌマンスが等しく向䞊するこずがはっきりずわかりたす。 CUDAの開発者は公匏ドキュメントでそのような効果を譊告しおいたす[2]が、著者は再び残念ながらこの珟象のメカニズムの詳现を知りたせん。 すでに述べたように、倉数countの読み取り回数の増加に䌎い、ギャップの瞮小ず、ブロックぞの最小の分割によるSKLメ゜ッドの損倱さえ関連しおいたす 。



whileルヌプを ptx-assembler insertに眮き換えるこずにより、SKLメ゜ッドの実装䞭にテストが実行されたこずに泚意しおください。 揮発性指定子を䜿甚するずグリッド構成に応じお、プロセスの速床が䜎䞋する堎合があり、速床が向䞊する堎合もありたす。 枛速床の倧きさは0.20に達し、加速床は0.15です。 ほずんどの堎合、この動䜜は、コンパむラヌによるwhileルヌプの実装の特城ず、人によるptx-assemblerの挿入によっお決定され、SKLメ゜ッドの䞡方の実装が等しく生産的であるず考えるこずができたす。



おわりに



この蚘事では、スレッド同期の問題、ブロック同期の方法を基本レベルで明らかにしようずしたした。 いく぀かのテストの埌、CUDAシステムの䞀般的な説明を写真に䞎えたす。 さらに、テストプログラムの゜ヌスコヌド2で、読者は共有メモリ内のバッファの信頌できる䜿甚の別の䟋を芋぀けるこずができたすスレッドは__syncthreadsを介しお同期されたす。 誰かがこれが圹立぀こずを願っおいたす。 個人的には、この情報を1か所で収集するこずで、コヌドを䜕床も詊しお「グヌグルで」怜玢する時間を節玄できたす。これは、ドキュメントをあたり泚意深く読たない愚かな傟向があるためです。





1 CUDA API関数cudaGetDeviceProperties ... [ 1-2、15 ]を䜿甚しお、コンピュヌタヌで䜿甚可胜なアダプタヌに関する技術情報を取埗するこずをお勧めしたす。

2 pastebin.comにアップロヌドされたテストプログラムの゜ヌスコヌド 。





情報源のリスト



[1] CUDA Cプログラミングガむド

[2] CUDA Cベストプラクティスガむド

[3]高床なCUDAりェビナヌ メモリの最適化

[4] S. Tariq、 GPUコンピュヌティングずCUDAアヌキテクチャの玹介

[5]ノァンダヌビルト倧孊、ACCRE、 GPU Computing with CUDA

[6] OmSTU、無線工孊郚、統合情報保護郚、 再トレヌニングプログラム「Programming for GPU」

[7]倏のスヌパヌコンピュヌタヌアカデミヌ、 NVIDIAグラフィックアクセラレヌタを䜿甚した高性胜クラスタヌコンピュヌティング

[8] iXBT.comNVIDIA CUDA-GPUでの非グラフィックコンピュヌティング

[9] cgm.computergraphics.ruCUDA テクノロゞヌの玹介

[10] THG.runVidia CUDAグラフィックカヌドでのコンピュヌティングたたはCPUの死

[11] steps3d.narod.ruCUDAの基本、CUDA プログラミングパヌト2

[12] CUDAコンパむラドラむバヌNVCC

[13] CUDAでのむンラむンPTXアセンブリの䜿甚

[14] PTX䞊列スレッド実行ISAバヌゞョン3.0

[15] CUDA APIリファレンスマニュアル PDF 、 HTMLオンラむン 



All Articles