C#でお手軽にOpenCLを使ったプログラムを作れないかと思い、OpenTKを使用。
OpenTK 3.xではOpenCLの機能がClooとして独立したようですが、現行のOpenTK 4.xでは再びOpenCLが統合されています。
まずはNugetパッケージマネージャからOpenTK 4.7.xをプロジェクトに追加します。
サンプルプログラムはhttps://github.com/opentk/opentk/tree/master/tests/OpenToolkit.OpenCL.Testsにあるので参照。
using OpenTK.Compute.OpenCL; using System.Text; CL.GetPlatformIds(0, null, out uint platformCount); CLPlatform[] platformIds = new CLPlatform[platformCount]; CL.GetPlatformIds(platformCount, platformIds, out _); Console.WriteLine(platformIds.Length); foreach (CLPlatform platform in platformIds) { Console.WriteLine(platform.Handle); CL.GetPlatformInfo(platform, PlatformInfo.Name, out byte[] val); Console.WriteLine(System.Text.Encoding.ASCII.GetString(val)); } foreach (IntPtr platformId in platformIds) { CL.GetDeviceIds(new CLPlatform(platformId), DeviceType.All, out CLDevice[] deviceIds); CLContext context = CL.CreateContext(IntPtr.Zero, (uint)deviceIds.Length, deviceIds, IntPtr.Zero, IntPtr.Zero, out CLResultCode result); if (result != CLResultCode.Success) { throw new Exception("The context couldn't be created."); } string code = @" __kernel void add(__global float* A, __global float* B,__global float* result, const float mul) { int i = get_global_id(0); for (int j =0;j < 60000;j++) { for (int k =0;k < 60000;k++) { result[i] = (A[i] + B[i])*mul; } } }"; CLProgram program = CL.CreateProgramWithSource(context, code, out result); CL.BuildProgram(program, (uint)deviceIds.Length, deviceIds, null, IntPtr.Zero, IntPtr.Zero); CLKernel kernel = CL.CreateKernel(program, "add", out result); int arraySize = 20; float[] A = new float[arraySize]; float[] B = new float[arraySize]; for (int i = 0; i < arraySize; i++) { A[i] = 1; B[i] = i; } CLBuffer bufferA = CL.CreateBuffer(context, MemoryFlags.ReadOnly | MemoryFlags.CopyHostPtr, A, out result); CLBuffer bufferB = CL.CreateBuffer(context, MemoryFlags.ReadOnly | MemoryFlags.CopyHostPtr, B, out result); float[] pattern = new float[] { 1, 3, 5, 7 }; CLBuffer resultBuffer = new CLBuffer(CL.CreateBuffer(context, MemoryFlags.WriteOnly, new UIntPtr((uint)(arraySize * sizeof(float))), IntPtr.Zero, out result)); try { CL.SetKernelArg(kernel, 0, bufferA); CL.SetKernelArg(kernel, 1, bufferB); CL.SetKernelArg(kernel, 2, resultBuffer); CL.SetKernelArg(kernel, 3, -1f); CLCommandQueue commandQueue = new CLCommandQueue( CL.CreateCommandQueueWithProperties(context, deviceIds[0], IntPtr.Zero, out result)); CL.EnqueueFillBuffer(commandQueue, bufferB, pattern, UIntPtr.Zero, (UIntPtr)(arraySize * sizeof(float)), null, out _); //CL.EnqueueNDRangeKernel(commandQueue, kernel, 1, null, new UIntPtr[] {new UIntPtr((uint)A.Length)}, // null, 0, null, out CLEvent eventHandle); CL.EnqueueNDRangeKernel(commandQueue, kernel, 1, null, new UIntPtr[] { new UIntPtr((uint)A.Length) }, null, 0, null, out CLEvent eventHandle); CL.Finish(commandQueue); CL.SetEventCallback(eventHandle, (int)CommandExecutionStatus.Complete, (waitEvent, data) => { float[] resultValues = new float[arraySize]; CL.EnqueueReadBuffer(commandQueue, resultBuffer, true, UIntPtr.Zero, resultValues, null, out _); StringBuilder line = new StringBuilder(); foreach (float res in resultValues) { line.Append(res); line.Append(", "); } Console.WriteLine(line.ToString()); }); //get rid of the buffers because we no longer need them CL.ReleaseMemoryObject(bufferA); CL.ReleaseMemoryObject(bufferB); CL.ReleaseMemoryObject(resultBuffer); //Release the program kernels and queues CL.ReleaseProgram(program); CL.ReleaseKernel(kernel); CL.ReleaseCommandQueue(commandQueue); CL.ReleaseContext(context); CL.ReleaseEvent(eventHandle); } catch (Exception e) { Console.WriteLine(e.ToString()); throw; } }
GPU上で実行されるプログラムはC言語で記述できます。
拍子抜けするぐらい簡単にOpenCLを使用したプログラムを作れるんですね。
どうしてもGPUへのデータ転送がボトルネックになりやすいので、高速化するのは中々難しいですが、機会があれば活用したいところ。