最もシンプルなOpenCLによる演算の実行 - 2
About
「最もシンプルなOpenCLによる演算の実行 - 1」から続く内容です。サンプルプロジェクトは前のページのものと同じものです。
OpenCL カーネルの生成
次に OpenCL の処理を実行するための OpenCL カーネルを生成します。カーネルの概念が分からないときは、カーネルはプログラムとデバイス間をやりとりするためのもの、あるいは、関数と引数がセットになったもの、程度に覚えておけば十分だと思います。
clCreateKernel 関数を利用して、OpenCL カーネルを生成します。この関数では対象のプログラムと実行する関数名を引数に指定していることに注目してください。実際に処理する内容は、カーネルで定義することになります。ここでは先の手順で用意した OpenCLC のプログラムに実装される関数 mul を指定しています (multiple≒乗算の略) 。
- 引数やその他の詳細などはサンプルプロジェクトないし公式から確認してください。
//カーネルの生成 IntPtr kernel = clCreateKernel(program, "mul", out errcode); if (errcode != CL_SUCCESS) throw new Exception("Error at clCreateKernel : " + errcode);
メモリ領域(バッファ)の用意
プログラムを用意して、そのプログラムをデバイス上で実行するためのカーネルも用意ができたので、残る必要な手順は、実行する関数の引数の用意になります。OpenCL による演算を実行するとき、演算であつかうデータ、例えば関数の引数などが保存されるメモリ領域は、あらかじめ確保しておいて、さらにそのメモリ領域への参照も維持しておく必要があります。OpenCL ではこのメモリ領域をバッファと呼びます。
ここで C/C++ によるプログラミングと、C#(.Net) によるプログラミングとで、環境の違いが発生します。C# (厳密には .Net) は所謂マネージドコードですから、ガベージコレクター/ガベージコレクション (GC) によって、メモリ領域が移動させられたり、片づけられたりします。OpenCL による処理の実行前、実行中に、OpenCL が参照するデータのメモリ領域が移動しては困るので、C# による OpenCL の開発では、それを防ぐ措置を取ることも必要になります。
今回のサンプルプログラムでは、xDataArray[] と yDataArray[] の乗算結果を変数 rDataArray[] に代入します。これらはそれぞれ OpenCL による処理の実行に必要な引数なので、それぞれのメモリ領域を移動しないように制御して、それぞれのバッファを用意します。
まずガベージコレクターによるメモリ領域の移動や削除を回避するために、GCHandle.Allocメソッドを利用します。これによって、プログラマが任意に解放するまでの間、ガベージコレクターは指定されたオブジェクトのメモリ領域を操作しません。さらに、メモリ領域へのポインタも取得することができるようになります。この操作は OpenCL 固有のものではなく、C# による実装に依存するものです。
次いで OpenCL が利用するバッファを生成します。バッファは clCreateBuffer 関数によって生成します。引数には OpenCL コンテキストの他に、そのバッファ領域をどのように利用するのかと、用意するバッファ領域の大きさを指定していることに注目してください。特に今回のサンプルプログラムでは、rDataArray に乗算結果を出力するので、そのバッファは書き込みを許可するようにしています。
- 引数やその他の詳細などはサンプルプロジェクトないし公式から確認してください。
//バッファの生成 //メモリ領域の固定 GCHandle gcHandle = GCHandle.Alloc(xDataArray, GCHandleType.Pinned); //バッファの生成 IntPtr buffer_xData = clCreateBuffer (context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, (uint)(Marshal.SizeOf(xDataArray[0]) * xDataArray.Length), gcHandle.AddrOfPinnedObject(), out errcode); //メモリ領域の固定を解除 gcHandle.Free(); if (errcode != CL_SUCCESS) throw new Exception("Error at clCreateBuffer : " + errcode); gcHandle = GCHandle.Alloc(yDataArray, GCHandleType.Pinned); IntPtr buffer_yData = clCreateBuffer (context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, (uint)(Marshal.SizeOf(yDataArray[0]) * yDataArray.Length), gcHandle.AddrOfPinnedObject(), out errcode); gcHandle.Free(); if (errcode != CL_SUCCESS) throw new Exception("Error at clCreateBuffer : " + errcode); gcHandle = GCHandle.Alloc(rDataArray, GCHandleType.Pinned); IntPtr buffer_rData = clCreateBuffer (context, CL_MEM_WRITE_ONLY, (uint)(Marshal.SizeOf(rDataArray[0]) * rDataArray.Length), IntPtr.Zero, out errcode); //再度固定して利用するので、ここでは解放せずに固定したままにしておく。 //gcHandle.Free(); if (errcode != CL_SUCCESS) throw new Exception("Error at clCreateBuffer : " + errcode);
引数の設定
引数に用いるデータのバッファを用意することができたので、OpenCL カーネルに、そのバッファを利用するように設定します。カーネルに対し引数を設定するためには、clSetKernelArg 関数を利用します。引数には OpenCL カーネルと、引数のインデックス、引数に与える値の大きさ、引数として渡すバッファを指定します。
- 引数やその他の詳細などはサンプルプロジェクトないし公式から確認してください。
//引数の設定 errcode = clSetKernelArg(kernel, 0, (uint)(Marshal.SizeOf(typeof(IntPtr))), ref buffer_xData); if (errcode != CL_SUCCESS) throw new Exception("Error at clSetKernelArg : " + errcode); errcode = clSetKernelArg(kernel, 1, (uint)(Marshal.SizeOf(typeof(IntPtr))), ref buffer_yData); if (errcode != CL_SUCCESS) throw new Exception("Error at clSetKernelArg : " + errcode); errcode = clSetKernelArg(kernel, 2, (uint)(Marshal.SizeOf(typeof(IntPtr))), ref buffer_rData); if (errcode != CL_SUCCESS) throw new Exception("Error at clSetKernelArg : " + errcode);
ここまでのまとめ
- OpenCL を利用して並列処理を実行するための、プラットフォームとデバイスを用意する。
- OpenCL コンテキストを用意する。
- OpenCLC で書かれたプログラムを用意して、読み込んで、ビルドする。
- OpenCL による処理を実行するためのカーネルの用意する。
- OpenCL によって利用されるメモリ領域=バッファの用意する。
- 用意したOpenCLカーネルに引数を設定する。