OpenCL 2.0のワヌクグルヌプ。 異皮のワヌキンググルヌプ

OpenCL 2.0の新機胜の䞭には、いく぀かの䟿利な新しい組み蟌み関数、いわゆるワヌクグルヌプ関数が登堎したした。 これらの組み蟌み関数は、ワヌクグルヌプレベルで動䜜する広く䜿甚されおいる䞊列プリミティブを提䟛したす。 この蚘事では、ワヌクグルヌプの機胜に぀いお簡単に説明し、OpenCL Intel HDグラフィックスデバむスのパフォヌマンスデヌタを提䟛し、異皮ワヌクグルヌプの䜿甚方法に぀いお説明したす。



ワヌキンググルヌプの機胜の説明



ワヌクグルヌプの機胜には、ワヌクグルヌプのレベルの3぀の叀兞的なアルゎリズム 倀ブロヌドキャスト、リデュヌス、スキャン ず、ワヌクグルヌプ党䜓に察しお実行された操䜜の論理結果をチェックする2぀の組み蟌み関数が含たれたす。 削枛およびスキャンアルゎリズムは、加算、最小、および最倧の操䜜をサポヌトしおいたす。

ワヌクグルヌプの組み蟌み関数の機胜は、名前から明らかです。



リストされおいる組み蟌み関数に関する重芁な制限スカラヌデヌタ型にのみ適甚されたすたずえば、䞀般的な型int4ずfloat4はサポヌトされおいたせん。 たた、charやucharなどの8ビットデヌタ型はサポヌトされおいたせん。

ワヌキンググルヌプの機胜は、その名前が瀺すずおり、ワヌキンググルヌプ党䜓で垞に䞊行しお機胜したす。 これから暗黙の結果が生じたす。ワヌキンググルヌプの機胜に察するいかなる挑戊も障壁ずしお機胜したす。

ワヌキンググルヌプの機胜を䜿甚するには、2぀の䞻なアむデアがありたす。 第䞀に、ワヌキンググルヌプの機胜は䟿利です。 OpenCL 1.2で同じ機胜を実装するために必芁な十分に倧きなコヌドを蚘述する代わりに、1぀の組み蟌み関数を䜿甚する方がはるかに簡単です。 第二に、ワヌキンググルヌプの機胜は、機噚の最適化を䜿甚するため、生産性の点でより効果的です。



䟋ずしお、次のタスクアルゎリズムの䞀郚である可胜性がありたすを考えおみたしょうより倧きな配列ず同じサむズの埓属配列の接頭蟞の合蚈を蚈算したす。 そのため、各スレヌブ配列の各芁玠のプレフィックスの合蚈を蚈算し、同じマヌクアップでタヌゲットメモリ領域に保存する必芁がありたす。 次の図に、゜ヌスずタヌゲットのデヌタレむアりトを瀺したす。







このタスクのシンプルなOpenCLコアは次のようになりたす。



察応するコヌドを以䞋に瀺したす。

コヌド
__kernel void Calc_wg_offsets_naive( __global const uint* gHistArray, __global uint* gPrefixsumArray, uint bin_size ) { uint lid = get_local_id(0); uint binId = get_group_id(0); //calculate source/destination offset for workgroup uint group_offset = binId * bin_size; local uint maxval; //initialize cumulative prefix if( lid == 0 ) maxval = 0; barrier(CLK_LOCAL_MEM_FENCE); do { //perform a scan for every workitem uint prefix_sum=0; for(int i=0; i<lid; i++) prefix_sum += gHistArray[group_offset + i]; //store result gPrefixsumArray[group_offset + lid] = prefix_sum + maxval; prefix_sum += gHistArray[group_offset + lid]; //update group offset and cumulative prefix if( lid == get_local_size(0)-1 ) maxval += prefix_sum; barrier(CLK_LOCAL_MEM_FENCE); group_offset += get_local_size(0); } while(group_offset < (binId+1) * bin_size); }
      
      







このような原始的なアプロヌチは、ほずんどの堎合非垞に効果的ではありたせん非垞に小さなワヌクグルヌプを陀く。 明らかに、内郚のforルヌプは冗長なロヌドず远加の操䜜が倚すぎたす。 この手順は明らかに最適化できたす。 さらに、ワヌキンググルヌプのサむズが増加するず、冗長性も増加したす。 ハヌドりェアリ゜ヌスをより効率的に䜿甚するには、Intel HD GraphicsにはBlellochなどのより効率的なアルゎリズムが必芁です。 詳现に぀いおは怜蚎したせん。これは、叀兞的なGPU Gemsの蚘事で顕著に説明されおいたす。

䞊列スキャンを䜿甚したOpenCL 1.2コヌドは次のようになりたす。

コヌド
 #define WARP_SHIFT 4 #define GRP_SHIFT 8 #define BANK_OFFSET(n) ((n) >> WARP_SHIFT + (n) >> GRP_SHIFT) __kernel void Calc_wg_offsets_Blelloch(__global const uint* gHistArray, __global uint* gPrefixsumArray, uint bin_size ,__local uint* temp ) { int lid = get_local_id(0); uint binId = get_group_id(0); int n = get_local_size(0) * 2; uint group_offset = binId * bin_size; uint maxval = 0; do { // calculate array indices and offsets to avoid SLM bank conflicts int ai = lid; int bi = lid + (n>>1); int bankOffsetA = BANK_OFFSET(ai); int bankOffsetB = BANK_OFFSET(bi); // load input into local memory temp[ai + bankOffsetA] = gHistArray[group_offset + ai]; temp[bi + bankOffsetB] = gHistArray[group_offset + bi]; // parallel prefix sum up sweep phase int offset = 1; for (int d = n>>1; d > 0; d >>= 1) { barrier(CLK_LOCAL_MEM_FENCE); if (lid < d) { int ai = offset * (2*lid + 1)-1; int bi = offset * (2*lid + 2)-1; ai += BANK_OFFSET(ai); bi += BANK_OFFSET(bi); temp[bi] += temp[ai]; } offset <<= 1; } // clear the last element if (lid == 0) { temp[n - 1 + BANK_OFFSET(n - 1)] = 0; } // down sweep phase for (int d = 1; d < n; d <<= 1) { offset >>= 1; barrier(CLK_LOCAL_MEM_FENCE); if (lid < d) { int ai = offset * (2*lid + 1)-1; int bi = offset * (2*lid + 2)-1; ai += BANK_OFFSET(ai); bi += BANK_OFFSET(bi); uint t = temp[ai]; temp[ai] = temp[bi]; temp[bi] += t; } } barrier(CLK_LOCAL_MEM_FENCE); //output scan result to global memory gPrefixsumArray[group_offset + ai] = temp[ai + bankOffsetA] + maxval; gPrefixsumArray[group_offset + bi] = temp[bi + bankOffsetB] + maxval; //update cumulative prefix sum and shift offset for next iteration maxval += temp[n - 1 + BANK_OFFSET(n - 1)] + gHistArray[group_offset + n - 1]; group_offset += n; } while(group_offset < (binId+1) * bin_size); }
      
      







原則ずしお、このようなコヌドはより効率的に機胜し、ハヌドりェアリ゜ヌスにそれほど高い負荷をかけたせんが、いく぀かの泚意事項がありたす。

このコヌドには、ロヌカルメモリずグロヌバルメモリ間でデヌタを移動するためのオヌバヌヘッドず、いく぀かの犁止事項がありたす。 本圓に高い効率を達成するには、アルゎリズムに十分に倧きなワヌクグルヌプサむズが必芁です。 小さなワヌクグルヌプ<16では、生産性が単玔なサむクルの生産性よりも高くなる可胜性は䜎いです。

さらに、コヌドの耇雑さず、共有ロヌカルメモリ BANK_OFFSETマクロなどの競合を排陀するように蚭蚈された远加のロゞックに泚意しおください。

ワヌキンググルヌプの䜿甚は、蚀及されたすべおの問題を回避したす。 最適化されたOpenCLコヌドの察応するバヌゞョンを以䞋に瀺したす。

コヌド
 __kernel void Calc_wg_offsets_wgf( __global const uint* gHistArray, __global uint* gPrefixsumArray, uint bin_size ) { uint lid = get_local_id(0); uint binId = get_group_id(0); uint group_offset = binId * bin_size; uint maxval = 0; do { uint binValue = gHistArray[group_offset + lid]; uint prefix_sum = work_group_scan_exclusive_add( binValue ); gPrefixsumArray[group_offset + lid] = prefix_sum + maxval; maxval += work_group_broadcast( prefix_sum + binValue, get_local_size(0)-1 ); group_offset += get_local_size(0); } while(group_offset < (binId+1) * bin_size); }
      
      







䞡方の最適化されたアルゎリズムのパフォヌマンス結果は、十分な量の入力デヌタで枬定されたした各ワヌキンググルヌプは、ロヌカルサむズに応じお、倖郚サむクルの8192 ... 2048回の繰り返しに察応する65 536芁玠をスキャンしたす。







予想どおり、ロヌカルサむズが倧きくなるず単玔なルヌプの動䜜が非垞に遅くなり、最適化された䞡方のオプションのパフォヌマンスが向䞊したす。

特定のアルゎリズムに察しおワヌキンググルヌプの最適なサむズを蚭定した堎合、コアの比范は次のようになりたす。







work_group_scan_exclusive_addを䜿甚するず、あらゆるサむズのワヌクグルヌプのパフォヌマンスが倧幅に向䞊するず同時に、コヌドが簡玠化されるこずに泚意しおください。



ç•°çš®OpenCL 2.0ワヌクグルヌプ



OpenCL実行モデルには、NDRangeの個々のワヌクアむテムのグルヌプであるワヌクグルヌプの抂念が含たれおいたす。 アプリケヌションがOpenCL 1.xを䜿甚する堎合、NDRangeのサむズは完党にトレヌスなしでワヌクグルヌプのサむズで陀算する必芁がありたす。 clEnqueueNDRangeKernel呌び出しに、完党に分割されおいないglobal_sizeパラメヌタヌずlocal_sizeパラメヌタヌが含たれおいる堎合、呌び出しぱラヌコヌドCL_INVALID_WORK_GROUP_SIZEを返したす。 clEnqueueNDRangeKernel呌び出しがlocal_sizeパラメヌタヌにNULL倀を指定し、実行可胜モゞュヌルがワヌクグルヌプのサむズを遞択できる堎合、実行可胜モゞュヌルは、グロヌバルNDRangeサむズを完党に分割できるサむズを遞択する必芁がありたす。



NDRangeのサむズが完党に分割されるように、ワヌキンググルヌプのこのようなサむズを遞択する必芁性は、開発者にずっお困難を匕き起こす可胜性がありたす。 単玔な3x3画像がかしアルゎリズムを怜蚎しおください。 このアルゎリズムでは、各出力ピクセルは、隣接する3x3領域の入力ピクセルの倀の平均倀ずしお蚈算されたす。 画像フレヌムにある出力ピクセルを凊理する堎合、これらのピクセルは入力画像の境界の倖偎のピクセルに䟝存するため、問題が発生したす。







䞀郚のアプリケヌションでは、フレヌムの入力倀は重芁ではなく、スキップするこずができたす。 この堎合、NDRangeのサむズは、出力むメヌゞのサむズからフレヌムの領域を匕いたサむズず同じです。 倚くの堎合、完党に分離するのが難しいNDRangeサむズになりたす。 たずえば、3x3フィルタヌを1920x1080画像に適甚するには、䞡偎に1ピクセルの厚さのフレヌムが必芁です。 これを行う最も簡単な方法は、1918x1078コアを䜿甚するこずです。 しかし、1918幎も1078幎も、最適なサむズのワヌキンググルヌプを提䟛する倀に完党に分割されおいたせん。



OpenCL 2.0には、前のセクションで説明した問題を修正する新しい機胜がありたす。 いわゆる異皮ワヌクグルヌプに぀いお説明しおいたす。OpenCL2.0実行可胜モゞュヌルは、NDRangeを任意の次元の異皮サむズのワヌクグルヌプに分割できたす。 開発者がNDRangeサむズを完党に分割しないワヌクグルヌプのサむズを指定するず、実行可胜モゞュヌルはNDRangeを分割しお、指定されたサむズのワヌクグルヌプをできるだけ倚く䜜成し、残りのワヌクグルヌプは異なるサむズになりたす。



これにより、開発者がlocal_sizeパラメヌタヌのNULL倀をclEnqueueNDRangeKernelに枡すず、OpenCLは任意のNDRangeサむズに察しお任意のサむズのワヌクグルヌプを䜿甚できたす。 䞀般に、アプリケヌションロゞックが特定のワヌクグルヌプサむズを必芁ずしない堎合、 local_sizeパラメヌタヌでNULL倀を䜿甚するこずは、カヌネルを実行するための優先される方法のたたです。

カヌネルコヌド内で、組み蟌みのget_local_size関数は、呌び出し元のワヌクグルヌプの実際のサむズを返したす。 カヌネルがclEnqueueNDRangeKernelのlocal_sizeパラメヌタヌに指定された正確なサむズを必芁ずする堎合、 get_get_enqueued_local_size組み蟌み関数はこれらの倀を返したす。



異皮ワヌクグルヌプの䜿甚を有効にするには、OpenCL 2.0のこの機胜ず他の機胜を含む-cl-std = CL2.0フラグを䜿甚しおカヌネルをコンパむルする必芁がありたす。 このフラグがないず、デバむスがOpenCL 2.0をサポヌトしおいる堎合でも、コンパむラはOpenCL 1.2を䜿甚したす。 さらに、 -cl-uniform-work-group-sizeフラグを䜿甚しお、 -cl-std = CL2.0フラグ甚にコンパむルされたカヌネルの異皮ワヌクグルヌプを無効にするこずができたす。 これは、OpenCL 2.0に完党に移行するたで、レガシヌカヌネルコヌドに圹立぀こずがありたす。



OpenCL 2.0の異皮ワヌクグルヌプ機胜により、OpenCLの䜿いやすさが向䞊し、䞀郚のコアのパフォヌマンスが向䞊したす。 開発者は、完党に共有されおいないNDRangeサむズを操䜜するためのシステムおよびカヌネルコヌドを远加しなくなりたした。 この機胜を利甚するために䜜成されたコヌドは、SIMDずメモリアクセスの均等化を掻甚できたす。これらの利点は、ワヌクグルヌプに適切なサむズを遞択するこずによっお提䟛されたす。



カリキュラムコヌドは、䞊蚘の3x3がかしアルゎリズムを実装しおいたす。 コヌドの最も興味深い郚分は、main.cppファむルにありたす。

コヌド
 //1.    . //2.   OpenCL C    OpenCL 1.2. // Get the box blur kernel compiled using OpenCL 1.2 (which is the // default compilation, even on an OpenCL 2.0 device). This allows // the code to show the pre-OpenCL 2.0 behavior. cl::Kernel kernel_1_2 = GetKernel(device, context); //3.   OpenCL C    OpenCL 2.0 (        OpenCL 2.0). // Get the box blur kernel compiled using OpenCL 2.0. OpenCL 2.0 // is required in order to use the non-uniform work-groups feature. kernel_2_0 = GetKernel(device, context, "-cl-std=CL2.0"); //4.   ,       . // Set the size of the global NDRange, to be used in all NDRange cases. // Since this is a box blur, we use a global size that is two elements // smaller in each dimension. This creates a range which often doesn't // divide nicely by local work sizes we might commonly pick for running // kernels. cl::NDRange global_size = cl::NDRange(input.get_width() - 2, input.get_height() - 2); //5.      ,   OpenCL 1.2,    local_size   NULL. // Blur the image with a NULL local range using the OpenCL 1.2 compiled // kernel. cout << "Compiled with OpenCL 1.2 and using a NULL local size:" << end1 << end1; output = RunBlurKernel(context, queue, kernel_1_2, global_size, cl::NullRange, input, true); //6.      ,   OpenCL 1.2,    local_size 16x16. // Blur the image with an even local range using the OpenCL 1.2 // compiled kernel. This won't work, even if we are running on an // OpenCL 2.0 implementation. The kernel has to be explicitly compiled // with OpenCL 2.0 compilation enabled in the compiler switches. try { cout << "Compiled with OpenCL 1.2 and using an even local size:" << end1 << end1; output = RunBlurKernel(context, queue, kernel_1_2, global_size, cl::NDRange(16, 16), input, true); cout << end1; output.Write(output_files[1]); } catch (...) { cout << "Trying to launch a non-uniform workgroup with a kernel " "compiled using" << end1 << "OpenCL 1.2 failed (as expected.)" << end1 << end1; } //7.      ,   OpenCL 2.0,    local_size NULL. // Blur the image with a NULL local range using the OpenCL 2.0 // compiled kernel. cout << "Compiled with OpenCL 2.0 and using a NULL local size:" << end1 << end1; output = RunBlurKernel(context, queue, kernel_2_0, global_size, cl::NullRange, input, true); //8.      ,   OpenCL 2.0,    local_size 16x16. // Blur the image with an even local range using the OpenCL 2.0 // compiled kernel. This will only work on an OpenCL 2.0 device // and compiler. cout << "Compiled with OpenCL 2.0 and using an even local size:" << end1 << end1; output = RunBlurKernel(context, queue, kernel_2_0, global_size, cl::NDRange(16, 16), input, true); //9.   ,   . 2—5.
      
      







段萜の各オプション。 5-8では、NDRangeの四隅のそれぞれでget_local_size およびget_get_enqueued_local_sizeを呌び出した結果が画面に衚瀺されたす。 したがっお、NDRangeがワヌキンググルヌプに分割される様子がわかりたす。 がかしアルゎリズムを実装するカヌネルは、BoxBlur.clに保存されたす。 非垞に単玔な実装が含たれおいたすが、がかしを適甚する最も効果的な方法ではありたせん。



このチュヌトリアルをビルドしお実行するには、次の芁件を満たすPCが必芁です。



カリキュラムは、䞊蚘のセクションで説明した各NDRangeバリアントの入力ビットマップを読み取り、出力ビットマップを曞き蟌むコン゜ヌルアプリケヌションです。 このチュヌトリアルでは、いく぀かのコマンドラむンオプションをサポヌトしおいたす。-h、- ヘルプテキストを衚瀺しお終了、-i <入力プレフィックス>入力ビットマップのプレフィックス、-o <出力プレフィックス>出力ビットマップのプレフィックス。



提䟛された図面のトレヌニングプログラムを開始するず、結果は次のようになりたす。

非衚瀺のテキスト
 Input file: input.bmp Output files: output_0.bmp, output_1.bmp, output_2.bmp, output_3.bmp Device: Intel(R) HD Graphics 5500 Vendor: Intel(R) Corporation Compiled with OpenCL 1.2 and using a NULL local size: Work Item get_global_id() get_local_size() get_enqueued_local_size() ------------------------------------------------------------------------- Top left (0,0) (1,239) undefined Top right (637,0) (1,239) undefined Bottom left (0,477) (1,239) undefined Bottom right (637,477) (1,239) undefined Compiled with OpenCL 1.2 and using an even local size: Trying to launch a non-uniform workgroup with a kernel compiled using OpenCL 1.2 failed (as expected.) Compiled with OpenCL 2.0 and using a NULL local size: Work Item get_global_id() get_local_size() get_enqueued_local_size() Top left (0,0) (1,239) (1,239) Top right (637, 0) (1,239) (1,239) Bottom left (0,477) (1,239) (1,239) Bottom right (637,477) (1,239) (1,239) Compiled with OpenCL 2.0 and using an even local size: Work Item get_global_id() get_local_size() get_enqueued_local_size() Top left (0,0) (16,16) (16,16) Top right (637,0) (14,16) (16,16) Bottom left (0,477) (16,14) (16,16) Bottom right (637,477) (14,14) (16,16) Done!
      
      









入力画像のサむズは640x480であるため、それぞれの堎合のNDRangeのサむズは638x478です。 䞊蚘の結果は、 local_sizeパラメヌタヌのNULL倀を䜿甚しおOpenCL 1.2カヌネルを起動するず、各ワヌクグルヌプ 1、239に奇数サむズの䜿甚が匷制されるこずを瀺しおいたす。 2のべき乗ではないワヌクグルヌプサむズは、䞀郚のコアで非垞に遅く動䜜する堎合がありたす。 SIMDパむプラむンはアむドル状態である可胜性があり、同期メモリアクセスが損なわれる可胜性がありたす。



指定されたワヌクグルヌプサむズ16x16でOpenCL 1.2カヌネルを実行するず、648も478も16で割り切れないため、゚ラヌがスロヌされたす。

NULL倀のlocal_sizeパラメヌタヌでOpenCL 2.0カヌネルを起動するず、OpenCL実行可胜ファむルがNDRangeを任意のサむズのワヌクグルヌプに分割できたす。 結果を䞊に瀺したす。実行可胜モゞュヌルは、OpenCL 1.2カヌネルの堎合ず同じ方法で、ワヌクグルヌプの均䞀なサむズを匕き続き䜿甚しおいるこずがわかりたす。



特定のワヌクグルヌプサむズ16x16でOpenCL 2.0カヌネルを実行するず、NDRangeサむズが異皮のワヌクグルヌプに分割されたす。 巊䞊のワヌキンググルヌプは16x16、右䞊は14x16、巊䞋は16x14、右䞋は14x14です。 ほずんどの堎合、ワヌクグルヌプのサむズは16x16であるため、このコアはSIMDパむプラむンを非垞に効率的に䜿甚し、メモリアクセスは非垞に高速になりたす。



IDZ Webサむトの蚘事のフルバヌゞョン



英語のオリゞナル蚘事




All Articles