てくメモ

trivial な notes

【C#】デリゲートと関数ポインタ

C#の関数ポインタは相互運用用?
単純にデリゲートに対して速くなったりしないだろうか?

先に結論:しなかった


要素から Key を取るコレクションを考えてみる。

private abstract class MyCollectionBase<T>
{
    public abstract int GetKeyMethod(T item);
}

private record class Data(int ID);

private unsafe class MyCollection : MyCollectionBase<Data>
{
    // 比較用クラスメソッド
    public override int GetKeyMethod(Data item) => item.ID;
}


デリゲートと関数ポインタを用意。

// デリゲート (Func<T, TRsult>)
public Func<Data, int> GetKeyDelegate;

// 関数ポインタ
public delegate*<Data, int> GetKeyFunctionPointer;


public MyCollection()
{
    GetKeyDelegate = (data) => data.ID;
    GetKeyFunctionPointer = &StaticMethod;
}

private static int StaticMethod(Data data) => data.ID;


検証用のメソッドを生やして……

public void AddRangeMethod(ReadOnlySpan<Data> data)
{
    foreach (var d in data) GetKeyMethod(d);
}

public void AddRangeDelegate(ReadOnlySpan<Data> data)
{
    foreach (var d in data) GetKeyDelegate(d);
}

public void AddRangeFunctionPointer(ReadOnlySpan<Data> data)
{
    foreach (var d in data) GetKeyFunctionPointer(d);
}


BenchmarkDotNet へGO!

MyCollection col = new();
private Data[] data = Enumerable.Range(0, 10).Select(v => new Data(v)).ToArray();

[Benchmark]
public void GetKeyMethod() => col.AddRangeMethod(data);

[Benchmark]
public void GetKeyDelegate() => col.AddRangeDelegate(data);

[Benchmark]
public void GetKeyFunctionalPointer() => col.AddRangeFunctionPointer(data);
                  Method |     Mean |    Error |   StdDev | Allocated |
------------------------ |---------:|---------:|---------:|----------:|
            GetKeyMethod | 22.18 ns | 1.574 ns | 0.086 ns |         - |
          GetKeyDelegate | 27.72 ns | 2.022 ns | 0.111 ns |         - |
 GetKeyFunctionalPointer | 28.21 ns | 5.758 ns | 0.316 ns |         - |


クラスメソッド最速はそれはそうという感じで、デリゲートと関数ポインタは同速とみなせそうに見える。

関数ポインタは通常の文脈で無闇に使うものでないのは間違いなさそう。(unsafe も付くし当たり前)