👌

★ "C# deligate" implement "java"

に公開

C#独自の概念であるdeligate処理をJavaにマイグレーションするとどうなるか?

deligateを理解するために、JavaへMigration

  • C#の"deligate"をJavaへMigrationする際、Javaには直接的な関数ポインタに相当する機能がないため、代わりに"Interface"や"ラムダ式"を活用することで、同等の動作を実現できる。

  • 以下は、C#のdeligate処理コードをJavaに変換した例。C#のdeligateが提供する柔軟な関数参照と呼び出しのメカニズムを、Javaでも効果的に再現することができる。

/* [java] */
import java.util.ArrayList;
import java.util.List;

public class SampleEx702 {

    // Actionインターフェースの定義
    interface Action {
        void execute(int a);
    }

    // 1つ目の処理
    static void func1(int a) {
        System.out.println("a=" + a);
    }

    // 2つ目の処理
    static void func2(int a) {
        System.out.println("a*2=" + (a * 2));
    }

    // 3つ目の処理
    static void func3(int a) {
        System.out.println("a*3=" + (a * 3));
    }

    public static void main(String[] args) {
        // Actionのリストを作成
        List<Action> actions = new ArrayList<>();

        // 処理の追加
        actions.add(SampleEx702::func1);
        actions.add(SampleEx702::func2);
        actions.add(SampleEx702::func3);

        // 処理の実行
        for (Action action : actions) {
            action.execute(3);
        }
    }
}
/* [cs] */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace SampleEx702
{
    class Program
    {
        //  デリゲートの宣言
        delegate void Action(int a);
        //  1つ目の処理
        static void Func1(int a)
        {
            Console.WriteLine("a={0}",a);
        }
        //  2つ目の処理
        static void Func2(int a)
        {
            Console.WriteLine("a*2={0}", a * 2);
        }
        //  3つ目の処理
        static void Func3(int a)
        {
            Console.WriteLine("a*3={0}", a * 3);
        }
        static void Main(string[] args)
        {
            //  デリゲートaの作成
            Action a = new Action(Func1);
            //  処理の追加
            a += new Action(Func2);
            a += new Action(Func3);
            //  処理の実行
            a(3);
        }
    }
}
a=3
a*2=6
a*3=9


並列処理版

/* [java] */
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SampleEx702 {

    // Actionインターフェースの定義
    interface Action {
        void execute(int a);
    }

    // 1つ目の処理
    static void func1(int a) {
        System.out.println("a=" + a);
    }

    // 2つ目の処理
    static void func2(int a) {
        System.out.println("a*2=" + (a * 2));
    }

    // 3つ目の処理
    static void func3(int a) {
        System.out.println("a*3=" + (a * 3));
    }

    public static void main(String[] args) {
        // Actionのリストを作成
        List<Action> actions = new ArrayList<>();

        // 処理の追加
        actions.add(SampleEx702::func1);
        actions.add(SampleEx702::func2);
        actions.add(SampleEx702::func3);

        // ExecutorServiceを作成
        ExecutorService executor = Executors.newFixedThreadPool(actions.size());

        // 処理の実行
        for (Action action : actions) {
            executor.execute(() -> action.execute(3));
        }

        // ExecutorServiceをシャットダウン
        executor.shutdown();
    }
}
/* [cs] */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx702
{
    class Program
    {
        //  デリゲートの宣言
        delegate void Action(int a);
        
        //  1つ目の処理
        static void Func1(int a)
        {
            Console.WriteLine("a={0}", a);
        }
        
        //  2つ目の処理
        static void Func2(int a)
        {
            Console.WriteLine("a*2={0}", a * 2);
        }
        
        //  3つ目の処理
        static void Func3(int a)
        {
            Console.WriteLine("a*3={0}", a * 3);
        }
        
        static void Main(string[] args)
        {
            //  デリゲートaの作成
            Action a = new Action(Func1);
            //  処理の追加
            a += new Action(Func2);
            a += new Action(Func3);
            
            //  処理の実行を並列で行う
            List<Task> tasks = new List<Task>();
            foreach (Action action in a.GetInvocationList())
            {
                tasks.Add(Task.Run(() => action(3)));
            }
            
            //  全てのタスクが完了するのを待つ
            Task.WaitAll(tasks.ToArray());
        }
    }
}


番外編:GPU使用

CLのkernel入れ替えを工夫した例。

  1. 関数型インターフェースの使用: Function<cl_mem, cl_kernel>を使って、カーネルを生成するための関数を引数として受け取る。
  2. カーネル生成メソッド: createKernelMultiplyByTwoとcreateKernelMultiplyByThreeメソッドを作成し、それぞれのカーネルを生成。
  3. 共通のカーネル作成メソッド: createKernelメソッドを作成し、カーネルのソースと名前を受け取ってカーネルを生成。
/* [java] Java 8以降*/
import org.jocl.*;

import java.util.function.Function;

// GPU処理のメソッド
static int gpuProcess(int a, Function<cl_mem, cl_kernel> kernelFunction) {
    // OpenCLの初期化
    CL.setExceptionsEnabled(true);
    cl_platform_id[] platforms = new cl_platform_id[1];
    CL.clGetPlatformIDs(1, platforms, null);
    cl_platform_id platform = platforms[0];

    cl_device_id[] devices = new cl_device_id[1];
    CL.clGetDeviceIDs(platform, CL.CL_DEVICE_TYPE_GPU, 1, devices, null);
    cl_device_id device = devices[0];

    cl_context context = CL.clCreateContext(null, 1, new cl_device_id[]{device}, null, null, null);
    cl_command_queue commandQueue = CL.clCreateCommandQueue(context, device, 0, null);

    // バッファの作成
    cl_mem memObjects = CL.clCreateBuffer(context, CL.CL_MEM_READ_WRITE | CL.CL_MEM_COPY_HOST_PTR, Sizeof.cl_int, new int[]{a}, null);

    // カーネルの作成
    cl_kernel kernel = kernelFunction.apply(memObjects);

    // カーネル引数の設定
    CL.clSetKernelArg(kernel, 0, Sizeof.cl_mem, Pointer.to(memObjects));

    // カーネルの実行
    long[] globalWorkSize = new long[]{1};
    CL.clEnqueueNDRangeKernel(commandQueue, kernel, 1, null, globalWorkSize, null, 0, null, null);

    // 結果の取得
    int[] result = new int[1];
    CL.clEnqueueReadBuffer(commandQueue, memObjects, CL.CL_TRUE, 0, Sizeof.cl_int, Pointer.to(result), 0, null, null);

    // リソースの解放
    CL.clReleaseMemObject(memObjects);
    CL.clReleaseKernel(kernel);
    CL.clReleaseCommandQueue(commandQueue);
    CL.clReleaseContext(context);

    return result[0];
}

// カーネルを生成するための関数
static cl_kernel createKernelMultiplyByTwo(cl_mem memObjects) {
    String programSource = "__kernel void multiplyByTwo(__global int* a) { a[0] = a[0] * 2; }";
    return createKernel(programSource, "multiplyByTwo", memObjects);
}

static cl_kernel createKernelMultiplyByThree(cl_mem memObjects) {
    String programSource = "__kernel void multiplyByThree(__global int* a) { a[0] = a[0] * 3; }";
    return createKernel(programSource, "multiplyByThree", memObjects);
}

// カーネルを作成する共通メソッド
static cl_kernel createKernel(String programSource, String kernelName, cl_mem memObjects) {
    cl_context context = CL.clGetCurrentContext();
    cl_program program = CL.clCreateProgramWithSource(context, 1, new String[]{programSource}, null, null);
    CL.clBuildProgram(program, 0, null, null, null, null);
    return CL.clCreateKernel(program, kernelName, null);
}

// 使用例
public static void main(String[] args) {
    int result1 = gpuProcess(5, memObjects -> createKernelMultiplyByTwo(memObjects));
    System.out.println("Result of multiplyByTwo: " + result1);

    int result2 = gpuProcess(5, memObjects -> createKernelMultiplyByThree(memObjects));
    System.out.println("Result of multiplyByThree: " + result2);
}
/* [cs] */
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using OpenCL.Net;

namespace SampleEx702
{
    class Program
    {
        // デリゲートの宣言
        delegate int Action(int a);

        // 1つ目の処理
        static int Func1(int a)
        {
            Console.WriteLine("CPU: a={0}", a);
            return a;
        }

        // 2つ目の処理
        static int Func2(int a)
        {
            Console.WriteLine("CPU: a*2={0}", a * 2);
            return a * 2;
        }

        // 3つ目の処理
        static int Func3(int a)
        {
            Console.WriteLine("CPU: a*3={0}", a * 3);
            return a * 3;
        }

        // GPU処理のメソッド
        static int GpuProcess(int a, string operation)
        {
            // OpenCLの初期化
            ErrorCode error;
            Cl.GetPlatformIDs(1, out cl_platform_id platform, out error);
            Cl.GetDeviceIDs(platform, DeviceType.Gpu, 1, out cl_device_id device, out error);
            cl_context context = Cl.CreateContext(null, 1, new[] { device }, null, IntPtr.Zero, out error);
            cl_command_queue commandQueue = Cl.CreateCommandQueue(context, device, 0, out error);

            // カーネルの作成
            string programSource = operation switch
            {
                "Func1" => "__kernel void func1(__global int* a) { a[0] = a[0]; }",
                "Func2" => "__kernel void func2(__global int* a) { a[0] = a[0] * 2; }",
                "Func3" => "__kernel void func3(__global int* a) { a[0] = a[0] * 3; }",
                _ => throw new ArgumentException("Invalid operation")
            };

            cl_program program = Cl.CreateProgramWithSource(context, 1, new[] { programSource }, null, out error);
            Cl.BuildProgram(program, 0, null, null, null, IntPtr.Zero);
            cl_kernel kernel = Cl.CreateKernel(program, operation, out error);

            // バッファの作成
            cl_mem memObjects = Cl.CreateBuffer(context, MemFlags.ReadWrite | MemFlags.CopyHostPtr, sizeof(int), new[] { a }, out error);

            // カーネル引数の設定
            Cl.SetKernelArg(kernel, 0, memObjects);

            // カーネルの実行
            IntPtr[] globalWorkSize = new IntPtr[] { new IntPtr(1) };
            Cl.EnqueueNDRangeKernel(commandQueue, kernel, 1, null, globalWorkSize, null, 0, null, out error);

            // 結果の取得
            int[] result = new int[1];
            Cl.EnqueueReadBuffer(commandQueue, memObjects, Bool.True, IntPtr.Zero, sizeof(int), result, 0, null, out error);

            // リソースの解放
            Cl.ReleaseMemObject(memObjects);
            Cl.ReleaseKernel(kernel);
            Cl.ReleaseProgram(program);
            Cl.ReleaseCommandQueue(commandQueue);
            Cl.ReleaseContext(context);

            return result[0];
        }

        static void Main(string[] args)
        {
            // デリゲートの作成
            Action actions = new Action(Func1);
            actions += new Action(Func2);
            actions += new Action(Func3);

            // GPU処理の実行
            List<Task<int>> gpuTasks = new List<Task<int>>();
            foreach (Action action in actions.GetInvocationList())
            {
                string operationName = action.Method.Name; // メソッド名を取得
                gpuTasks.Add(Task.Run(() => GpuProcess(3, operationName)));
            }

            // 全てのタスクが完了するのを待つ
            Task.WaitAll(gpuTasks.ToArray());

            // 結果の表示
            foreach (var task in gpuTasks)
            {
                Console.WriteLine("GPU Result: {0}", task.Result);
            }
        }
    }
}

参考サイト

Discussion