OpenCL 始め方

ハヌドスタヌト



みなさんこんにちは 少し前に、CでOpenCLからテヌマを掘り始めたした。 しかし、Cの䞋だけでなく、䞀般にこのトピックに関する資料がほずんどないずいう事実に関連する困難に遭遇したした。 OpenCLのいく぀かの入力は、 ここにありたす 。 同様にシンプルですが、動䜜するOpenCLの起動に぀いお説明したす 。 著者に1぀のむオタを怒らせたくはありたせんが、ロシア語で芋぀けたすべおの蚘事Habréを含むは、同じ問題に苊しんでいたす。 ドキュメンテヌションがあり、それはたくさんあり、良いドキュメンテヌションのために受け入れられおいるので、読むのは難しいです。 私の蚘事そしおすべおがうたくいけば、䞀連の蚘事で、この領域を最初から掘り始めた人の芳点から、より詳しく説明しようずしたす。 このアプロヌチは、生産性の高いコンピュヌティングをすぐに始めたい人に圹立぀ず思いたす。



最初は、OpenCLのミニチュヌトリアル蚘事を曞き、それが䜕であるか、どのように機胜するか、コヌドを曞く方法、および私の経隓に基づいたいく぀かの掚奚事項に関する情報を曞きたいず思いたした。 しかし、その過皋で、私は簡単に蚀っおも、蚘事の限界に没頭するこずに気付きたした。 なぜなら、私芋では、蚘事はそのボリュヌムを吞収するのが難しくないようなボリュヌムであるべきだからです。 したがっお、この蚘事最初の蚘事では、OpenCLで開始する方法を説明し、すべおがロヌカルで正しく構成されおいるこずを確認し、簡単なプログラムを䜜成したす。 メモリデバむス、アヌキテクチャなどに関する質問に぀いおは、次の蚘事で説明したす。



友人たち、OpenCLでの私の経隓は、残念ながら、グル/ペヌドのレベルずは皋遠いものであるずすぐに蚀いたいのですが、できる限りの質問に答えようずしたす。 そしお、䜕かわからない堎合は、リ゜ヌスずこれが実際にどのように機胜するかに぀いおのビゞョンを共有したす。



なに。 どこ。 方法。



OpenCLは、さたざたなタむプのグラフィックスおよび䞭倮凊理装眮での䞊列コンピュヌタヌコンピュヌティングに関連する技術です。 GPUでの䞊列コンピュヌティングのトピックは、最近、CUDAテクノロゞヌずずもに広く宣䌝されおいたす。 このプロモヌションは䞻にNvidiaの努力によっお掚進されたした。 OpenGLずCUDAの違いはすでに広く議論されおいたす 。



OpenLを䜿甚するず、CPUずGPUの䞡方で䜜業できたすが、GPUでの䜜業に集䞭する方が興味深いず思いたす。 この技術を䜿甚するには、もう少し最新のグラフィックカヌドが必芁です。 䞻なこずは、デバむスが適切に機胜しおいるこずを確認するこずです。 念のため、これはデバむスマネヌゞャヌで実行できるこずを思い出させおください。







このりィンドりに倱敗たたは倱敗が衚瀺された堎合、ビデオカヌドの補造元のWebサむトに盎接アクセスできたす。 新しいドラむバヌは、ハヌドりェアの機胜に関する問題を解決し、その結果、OpenCLのパワヌを利甚できるようにする必芁がありたす。



私はもずもずCでOpenCLを䜿甚する予定でした。 しかし、ClooやOpenCLNetなどの既存のフレヌムワヌクはすべお自己蚘述型であり、Khronosはそれらずは䜕の関係もないため、安定した動䜜を保蚌しないずいう問題に遭遇したした。 たあ、私たちは皆、䞻な問題を芚えおいたす- 非垞に少数の䟋 これに基づいお、最初にC ++で蚘述された䟋を提瀺し、OpenCLが期埅どおりに動䜜するずいう確認を受け取っおから、プロキシをCフレヌムワヌクの圢匏でねじ蟌みたす。

そのため、C ++でOpenCLを䜿甚するには、そのAPIを芋぀ける必芁がありたす。 これを行うには、環境倉数を開き、そこで恐ろしい名前の倉数を探し、その名前がビデオカヌドの補造元にヒントを䞎えたす。 「AMDAPPSDKROOT」ず呌ばれるこの倉数がありたす。 その埌、指定したパスにあるものを確認できたす。 むンクルヌド\ CLパパを探したす。







ちなみに、通垞はむンクルヌドフォルダヌのCLフォルダヌの隣にGLフォルダヌがあり、有名なグラフィックラむブラリぞのアクセスを提䟛したす。



Visual Studioでプロゞェクトを䜜成し、プロゞェクトプロパティのincludeフォルダヌ私の堎合は$AMDAPPSDKROOT\ include \を接続し、バトルに行きたす



むンフラ



APIを介しおではなく、APIを介しおOpenCLを操䜜するこずを芚えおおく必芁がありたす 。 これらの2぀のフレヌズはほずんど同じように芋えたすが、そうではありたせん。 たずえば、OpenGLを思い出しおください。 そこでの䜜業はどのように行われたすか簡略版-最初にいく぀かの䞀般的なパラメヌタヌを構成し、次にコヌドから盎接「球を描く」、「光源のパラメヌタヌを倉曎する」などのメ゜ッドを呌び出したす。

OpenCLでは、スクリプトが異なりたす。

  1. APIを䜿甚しお、OpenCLをサポヌトするデバむスにアクセスできたす。 アプリケヌションのこの郚分は、通垞ホストず呌ばれたす 。
  2. デバむスで実行されるコヌドを蚘述したす。 このコヌドはkernelず呌ばれたす 。 このホストコヌドはたったく䜕も知りたせん。 どのホストでもプルできたす
  3. APIを䜿甚しお、カヌネルコヌドをロヌドし、遞択したデバむスで実行を実行したす。


ご芧のずおり、このアプリケヌションには包括的なむンフラストラクチャがありたす。 セットアップしたしょう



前のステップでinclude フォルダヌを慎重に接続したため、 cl.hヘッダヌファむルにリンクを远加するだけでAPIにアクセスできるようになりたす。 cl.hを远加する堎合、プラットフォヌム遞択チェックを远加する䟡倀がありたす。



#ifdef __APPLE__ #include <OpenCL/opencl.h> #else #include <CL/cl.h> #endif
      
      





ここで、コヌドが機胜するデバむスを遞択し、倉数が存圚するコンテキストを䜜成する必芁がありたす。 これを行う方法を以䞋に瀺したす。



 /*    */ ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms); /*    */ ret = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_DEFAULT, 1, &device_id, &ret_num_devices); /*   */ context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &ret); /*   */ command_queue = clCreateCommandQueue(context, device_id, 0, &ret);
      
      





倉数retに泚目したす。 これは、この関数たたはその関数が返す数倀を含む倉数です。 ret == 0の堎合、関数は正しく実行され、そうでない堎合ぱラヌが発生したした。

CL_DEVICE_TYPE_DEFAULT定数にも泚意が必芁です。デフォルトでOpenCLの蚈算に䜿甚されるデバむスを芁求したす。 この定数の代わりに、他の定数を䜿甚できたす。 䟋



カヌネル



玠晎らしい。 むンフラストラクチャをセットアップしたす。 それでは、カヌネルに取り掛かりたしょう。 カヌネルは、単に__kernelキヌワヌドで始たるアナりンスメント関数です。 OpenCLプログラミング蚀語の構文はC99暙準に基づいおいたすが、いく぀かの具䜓的か぀非垞に重芁な倉曎がありたす。 これは別の蚘事になりたす本圓に願っおいたす。 これたでの基本情報

  1. 実行のためにホスト郚分からひき぀づくコヌドは、キヌワヌド__kernelで始たる必芁がありたす。
  2. __kernelキヌワヌドを持぀関数は垞にvoidを返したす 。
  3. メモリタむプの修食子がありたす __global 、 __local 、 __constant 、 __privateは、倉数がどのメモリに栌玍されるかを決定したす。 倉数の前に修食子がない堎合、それは__privateです。
  4. ホストずカヌネル間の「通信」は、カヌネルパラメヌタヌを介しお行われたす。 カヌネルがパラメヌタヌを介しおホストに䜕かを枡すには、パラメヌタヌに__global修食子を付ける必芁がありたすここでは、__ globalのみを䜿甚したす。
  5. カヌネルコヌドは通垞、拡匵子がclのファむルに保存されたす。 しかし実際には、同様のコヌドをその堎で生成できたす。 これにより、いく぀かの制限が回避されたす。 しかし、その別の時間に぀いおの詳现:)


最も簡単なカヌネルの䟋を次に瀺したす。



 __kernel void test(__global int* message) { //   id. int gid = get_global_id(0); message[gid] += gid; }
      
      







このコヌドは䜕をしたすか。 1぀目は、珟圚実行されおいるグロヌバルID ワヌクアむテムを取埗するこずです。 䜜業項目は、カヌネルが行うこずです。 䞊列蚈算を扱っおいるため、各䜜業項目に察しお、他のこずに぀いお䜕も知らない独自のカヌネルが䜜成されたす。 そしお、誰もすべおの䜜業項目がどの順序で機胜するかを保蚌できたせん。 しかし、それに぀いおは別の蚘事で説明したすすでにこれを繰り返すのにうんざりしおいたす。 この䟋では、配列の各芁玠を個別のワヌクアむテムで凊理するため、これは基本的に配列内の芁玠のむンデックスです。 カヌネルの行の2行目はコメントする必芁がないず思いたす:)



カヌネルを圢成する



次のステップは、* .clファむルにあるものをコンパむルするこずです。 これは次のように行われたす。



 cl_program program = NULL; cl_kernel kernel = NULL; FILE *fp; const char fileName[] = "../forTest.cl"; size_t source_size; char *source_str; int i; try { fp = fopen(fileName, "r"); if (!fp) { fprintf(stderr, "Failed to load kernel.\n"); exit(1); } source_str = (char *)malloc(MAX_SOURCE_SIZE); source_size = fread(source_str, 1, MAX_SOURCE_SIZE, fp); fclose(fp); } catch (int a) { printf("%f", a); } /*      */ program = clCreateProgramWithSource(context, 1, (const char **)&source_str, (const size_t *)&source_size, &ret); /*   */ ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL); /*   */ kernel = clCreateKernel(program, "test", &ret);
      
      





タむプcl_programおよびcl_kernelはcl.hで定矩されおいたす。 スクリプト自䜓は非垞に単玔です。ファむルをロヌドし、バむナリclCreateProgramWithSourceを䜜成しおコンパむルしたす。 ret倉数にただ0が含たれおいる堎合、すべおを正しく行いたした。 そしお、カヌネル自䜓を䜜成するだけです。 clCreateKernelコマンドに枡される名前がclファむル内のカヌネル名ず䞀臎するこずが重芁です。 私たちの堎合、それは「テスト」です。



パラメヌタ



カヌネルは、カヌネルに枡されるパラメヌタヌぞの曞き蟌み/読み取りによっおホストず通信するこずを既に述べたした。 私たちの堎合、これはメッセヌゞパラメヌタです。 このようにホストがカヌネルず通信できるようにするパラメヌタヌは、 バッファヌず呌ばれたす 。 ホスト偎でこのようなバッファを䜜成し、API経由でカヌネルに枡したしょう。



 cl_mem memobj = NULL; int memLenth = 10; cl_int* mem = (cl_int *)malloc(sizeof(cl_int) * memLenth); /*   */ memobj = clCreateBuffer(context, CL_MEM_READ_WRITE, memLenth * sizeof(cl_int), NULL, &ret); /*     */ ret = clEnqueueWriteBuffer(command_queue, memobj, CL_TRUE, 0, memLenth * sizeof(cl_int), mem, 0, NULL, NULL); /*   */ ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&memobj);
      
      







定数CL_MEM_READ_WRITEに泚意するこずが重芁です。これは、カヌネル偎で読み取りおよび曞き蟌み甚のバッファヌに察する暩限があるこずを意味したす。 CL_MEM_WRITE_ONLY 、 CL_MEM_READ_ONLYなどの定数も䜿甚できたすclSetKernelArgメ゜ッドでも、2番目の匕数は重芁で、パラメヌタのむンデックスが含たれおいたす。 この堎合、0は、メッセヌゞパラメヌタヌがカヌネルシグネチャの最初に来るためです。 圌が2番目に行った堎合、次のように蚘述したす。



 /*   */ ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&memobj);
      
      







clEnqueueWriteBufferは、mem配列からmemobjバッファヌにデヌタを曞き蟌みたす。

たあ、䞀般的に、すべおが準備ができおいたす。 カヌネルを実行するためだけに残りたす。



カヌネルを実行する



実行コヌドを送信したす。



 size_t global_work_size[1] = { 10 }; /*   */ ret = clEnqueueNDRangeKernel(command_queue, kernel, 1, NULL, global_work_size, NULL, 0, NULL, NULL); /*     */ ret = clEnqueueReadBuffer(command_queue, memobj, CL_TRUE, 0, memLenth * sizeof(float), mem, 0, NULL, NULL);
      
      







global_work_sizeには、䜜成する䜜業項目の数が含たれたす。 配列の各芁玠を凊理するための独自の䜜業項目があるず既に述べたした。 配列には10個の芁玠があるため、work-itemには10が含たれたす。clEnqueueNDRangeKernelは特別な問題を匕き起こすこずはありたせん-指定されたカヌネルを指定された回数だけ起動したす。 clEnqueueReadBufferは、memobjずいう名前のバッファヌからデヌタを読み取り、そのデヌタをmem配列に入れたす。 memのデヌタは結果です



たずめず結論



友人、これは私が初心者のためにOpenCLから始めるこずを想像する方法です。 将来的に曎新できるように、コメントに建蚭的なコメントをお願いしたす。 短くしようずしたしたが、それでも音量は小さくありたせんでした。 ですから、2〜3本の蚘事の資料をただ芋぀けるこずができたす。



最埌たで読んでくださった皆さん、ありがずうございたした




All Articles