忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

C#でOpenCL入門 (Cloo版) スレッドとグループの個数

スレッドとグループの個数

こちらも合わせてお読みください。


GPGPUでは普通複数のスレッドを使います。
複数のスレッドで仕事をバーっとやってもらうのです。

そのスレッドの個数はCPU側で設定します。
ときに、GPU側でもスレッドの個数がいくつかを知りたいことがあります。

それには、get_global_size()関数を使います。
この関数は、今のコマンドで、スレッドが全部でいくつ実行されているかを取得します。
参考

size_t get_global_size(uint dimIndex)

dimIndexは、次元を表すインデックスです。X軸方向のスレッド個数を取得したいなら0,Y軸方向なら1,Zなら2です。

サンプルコード

Program.cs

using Cloo;
using System.Linq;

class Program
{
    static void Main()
    {
        ComputePlatform platform = ComputePlatform.Platforms[0];
        ComputeDevice[] devices = platform
            .Devices
            .Where(d => d.Type == ComputeDeviceTypes.Gpu)
            .ToArray();
        ComputeContext context = new ComputeContext(
            devices,
            new ComputeContextPropertyList(platform),
            null, 
            System.IntPtr.Zero
            );
        ComputeProgram program = new ComputeProgram(
            context,
            System.IO.File.ReadAllText("myKernelProgram.cl")
            );
        program.Build(devices, null, null, System.IntPtr.Zero);
        const int elementCount = 6;
        ComputeBuffer<float> buffer = new ComputeBuffer<float>(
            context, 
            ComputeMemoryFlags.ReadWrite,
            elementCount
            );

        ComputeKernel kernel = program.CreateKernel("myKernelFunction");
        kernel.SetMemoryArgument(0, buffer);
        ComputeCommandQueue commandQueue = new ComputeCommandQueue(
            context,
            devices[0],
            ComputeCommandQueueFlags.None
            );

        commandQueue.Execute(
            kernel,
            null, 
            new long[] { 2, 3 },
            new long[] { 1, 1 }, 
            null
            );

        var dataFromGpu = new float[elementCount];
        commandQueue.ReadFromBuffer(
            buffer,
            ref dataFromGpu,
            true,
            null
            );

        foreach (var item in dataFromGpu)
        {
            System.Console.WriteLine(item);
        }

        commandQueue.Dispose();
        kernel.Dispose();
        buffer.Dispose();
        program.Dispose();
        context.Dispose();
    }
}

myKernelProgram.cl

__kernel void myKernelFunction(__global float* items)
{
	items[get_global_id(0)] = 
		get_global_size(0);
		//get_global_size(1);
}

このプログラムは、スレッドを(2×3)個作ります。 そして、バッファの先頭に、0番目の次元のスレッド個数を書き込みます。 ここでは0番目の次元を書き込んでいるので、2という数字(2×3の2)を書き込みます。

実行結果はこうなります。

2
2
0
0
0
0

myKernelProgram.clのコメントアウト位置を変えると、今度は1番目の次元の個数を書き込みます。 そうすると、バッファの先頭には3が書きこまれます(2×3の3です)。

3
3
0
0
0
0

グループのサイズ

以上では、スレッドの総数をGPU側で取得しました。
しかし、1グループ内のスレッド数を取得したい場合もあります。
それにはget_local_size関数を使います。
参考

size_t get_local_size(uint dimIndex)

dimIndexは取得するサイズの次元です。

たとえばスレッドを{{スレッド1,スレッド2、スレッド3}、{スレッド4、スレッド5,スレッド6}}と実行したとします。
全部で6つ。
1グループ3スレッドです。
そいう言う場合は、get_local_size(0)が3になります。
1グループ内のスレッド数を取得するのです。
サンプルを次のように改変します:

Program.cs

using Cloo;
using System.Linq;

class Program
{
    static void Main()
    {
        ComputePlatform platform = ComputePlatform.Platforms[0];
        ComputeDevice[] devices = platform
            .Devices
            .Where(d => d.Type == ComputeDeviceTypes.Gpu)
            .ToArray();
        ComputeContext context = new ComputeContext(
            devices,
            new ComputeContextPropertyList(platform),
            null, 
            System.IntPtr.Zero
            );
        ComputeProgram program = new ComputeProgram(
            context,
            System.IO.File.ReadAllText("myKernelProgram.cl")
            );
        program.Build(devices, null, null, System.IntPtr.Zero);
        const int elementCount = 24;
        ComputeBuffer<float> buffer = new ComputeBuffer<float>(
            context, 
            ComputeMemoryFlags.ReadWrite,
            elementCount
            );

        ComputeKernel kernel = program.CreateKernel("myKernelFunction");
        kernel.SetMemoryArgument(0, buffer);
        ComputeCommandQueue commandQueue = new ComputeCommandQueue(
            context,
            devices[0],
            ComputeCommandQueueFlags.None
            );

        commandQueue.Execute(
            kernel,
            null, 
            new long[] { 4, 6 },
            new long[] { 2, 3 }, 
            null
            );

        var dataFromGpu = new float[elementCount];
        commandQueue.ReadFromBuffer(
            buffer,
            ref dataFromGpu,
            true,
            null
            );

        foreach (var item in dataFromGpu)
        {
            System.Console.WriteLine(item);
        }

        commandQueue.Dispose();
        kernel.Dispose();
        buffer.Dispose();
        program.Dispose();
        context.Dispose();
    }
}

myKernelProgram.cl

__kernel void myKernelFunction(__global float* items)
{
	items[get_global_id(0)] = 
		get_local_size(0);
		//get_local_size(1);
}

これを実行すると次のようになります:

2
2
2
2
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0

このプログラムはまず4×6このスレッドを作り、2×3のスレッドを1グループにして、グループ分けしています。 そしてバッファの先頭に、ローカルサイズ(1グループのスレッド数)を書き込みます。 この場合は1グループにX方向には2ある(2×3の2)ので、2が書きこまれています。

グループの個数

スレッドが次のように実行されているとしましょう:
{{スレッド0、スレッド1}、{スレッド2、スレッド3}、{スレッド4、スレッド5}}
このとき、グループの個数は3個です。

グループの個数を得るには、get_num_groups()関数を使います。 参考

size_t get_num_groups(uint dimIndex)

dimIndexは次元のインデックスです。X軸方向のグループの数を得たい時には0。Y軸方向なら1。Zなら2です。

Program.cs

using Cloo;
using System.Linq;

class Program
{
    static void Main()
    {
        ComputePlatform platform = ComputePlatform.Platforms[0];
        ComputeDevice[] devices = platform
            .Devices
            .Where(d => d.Type == ComputeDeviceTypes.Gpu)
            .ToArray();
        ComputeContext context = new ComputeContext(
            devices,
            new ComputeContextPropertyList(platform),
            null, 
            System.IntPtr.Zero
            );
        ComputeProgram program = new ComputeProgram(
            context,
            System.IO.File.ReadAllText("myKernelProgram.cl")
            );
        program.Build(devices, null, null, System.IntPtr.Zero);
        const int elementCount = 24;
        ComputeBuffer<float> buffer = new ComputeBuffer<float>(
            context, 
            ComputeMemoryFlags.ReadWrite,
            elementCount
            );

        ComputeKernel kernel = program.CreateKernel("myKernelFunction");
        kernel.SetMemoryArgument(0, buffer);
        ComputeCommandQueue commandQueue = new ComputeCommandQueue(
            context,
            devices[0],
            ComputeCommandQueueFlags.None
            );

        commandQueue.Execute(
            kernel,
            null, 
            new long[] { 2, 12 },
            new long[] { 2, 3 }, 
            null
            );

        var dataFromGpu = new float[elementCount];
        commandQueue.ReadFromBuffer(
            buffer,
            ref dataFromGpu,
            true,
            null
            );

        foreach (var item in dataFromGpu)
        {
            System.Console.WriteLine(item);
        }

        commandQueue.Dispose();
        kernel.Dispose();
        buffer.Dispose();
        program.Dispose();
        context.Dispose();
    }
}

myKernelProgram.cl

__kernel void myKernelFunction(__global float* items)
{
	items[get_global_id(0)] = 
		get_num_groups(0);
		//get_num_groups(1);
}

このプログラムは、2×12個のスレッドを作り、それを2×3のスレッドを持つグループでグループ分けしています。 そうすると、グループの数は1×4となります。 結果、このプログラムは次のような文字列を出力します:

1
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0

グループの数は1×4なので、その最初の1を出力したのです。 もしmyKernelProgram.clのコメントアウト位置を変えれば、4を出力するでしょう。

拍手[0回]

PR