C#中函数指针

起因

函数指针在C# 9为了提高性能而加入.还有可以更好的与C/C++进行交互.函数指针是在需要性能的地方代替委托,因为C#官方文档做的很好,这里就不对C#的函数指针进行过多的介绍.

委托和函数指针使用对比

unsafe static void Main(string[] args)
{
    //委托
    Action<string> action1 = Console.WriteLine;
    action1("123");

    //函数指针 要在unsafe作用域内,才可以使用      //函数指针 在delegate关键字后加* ,后跟参数类型和返回值类型
    delegate*<string, void> action2 = &Console.WriteLine;
    action2("hello pointer");

    Console.ReadLine();
}

注意事项:

  1. 函数指针只能在 unsafe 上下文中声明
  2. 目前只能在标记 static 函数上使用 & 运算符获取函数的地址

反编译程序,查看IL代码:

// Action<string> action = Console.WriteLine;
IL_0001: ldnull
IL_0002: ldftn void[System.Console] System.Console::WriteLine(string)
IL_0008: newobj instance void class [System.Runtime] System.Action`1<string>::.ctor(object, native int)
IL_000d: stloc.0
// action("123");
IL_000e: ldloc.0
IL_000f: ldstr "123"
IL_0014: callvirt instance void class [System.Runtime] System.Action`1<string>::Invoke(!0)  //委托调用
// ((delegate*<string?, void>)(&Console.WriteLine))("hello pointer");
IL_0019: nop
IL_001a: ldftn void[System.Console] System.Console::WriteLine(string) //函数指针指向WriteLine
IL_0020: stloc.1
IL_0021: ldloc.1
IL_0022: stloc.2
IL_0023: ldstr "hello pointer"
IL_0028: ldloc.2
// (no C# code)
IL_0029: calli void (string)    //调用函数指针指向的函数入口地址

看到委托调用使用的IL指令时callvirt(可以调用实例方法和虚方法),函数指针调用使用的指令为calli

使用Benchmark.DotNet进行测试委托和函数指针的性能

[DisassemblyDiagnoser(printSource: true, maxDepth: 3)]
public class FunctionPointer
{
    private static int Add(int a, int b)
    {
        return a + b;
    }

    [Benchmark(Baseline = true)]
    public void DelegateTest()
    {
        Func<int, int, int> func = Add;
        for (int i = 0; i < 100; i++)
        {
            int sum = 0;
            for (int j = 0; j < 100000; j++)
            {
                sum += func(1, 2);
            }
        }
    }


    [Benchmark]
    public unsafe void FunctionPointerTest()
    {
        delegate*<int, int, int> func = &Add;
        for (int i = 0; i < 100; i++)
        {
            int sum = 0;
            for (int j = 0; j < 100000; j++)
            {
                sum += func(1, 2);
            }
        }
    }
}

在c#中函数指针比使用委托的性能更好

这是在笔记本上测试结果,在另外一台机器上, .Net 6整体性能都比.Net 5要好

秋风 2021-11-03