委托在 C# 中出现意外性能不佳

本文关键字:意外 性能 | 更新日期: 2023-09-27 18:34:32

我之前发布了关于用 C# 动态编译代码的问题,答案引出了另一个问题。

一个建议是我使用代表,我尝试过,他们工作得很好。但是,它们的长凳速度比直接呼叫慢约 8.4 倍,这毫无意义。

这段代码有什么问题?

我的结果,.Net 4.0,64 位,直接运行 exe:62、514、530

public static int Execute(int i) { return i * 2; }
private void button30_Click(object sender, EventArgs e)
{
    CSharpCodeProvider foo = new CSharpCodeProvider();
    var res = foo.CompileAssemblyFromSource(
        new System.CodeDom.Compiler.CompilerParameters()
        {
            GenerateInMemory = true,
            CompilerOptions = @"/optimize",                    
        },
        @"public class FooClass { public static int Execute(int i) { return i * 2; }}"
    );
    var type = res.CompiledAssembly.GetType("FooClass");
    var obj = Activator.CreateInstance(type);
    var method = type.GetMethod("Execute");
    int i = 0, t1 = Environment.TickCount, t2;
    //var input = new object[] { 2 };
    //for (int j = 0; j < 10000000; j++)
    //{
    //    input[0] = j;
    //    var output = method.Invoke(obj, input);
    //    i = (int)output;
    //}
    //t2 = Environment.TickCount;
    //MessageBox.Show((t2 - t1).ToString() + Environment.NewLine + i.ToString());
    t1 = Environment.TickCount;
    for (int j = 0; j < 100000000; j++)
    {
        i = Execute(j);
    }
    t2 = Environment.TickCount;
    MessageBox.Show("Native: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());
    var func = (Func<int, int>) Delegate.CreateDelegate(typeof (Func<int, int>), method);
    t1 = Environment.TickCount;
    for (int j = 0; j < 100000000; j++)
    {
        i = func(j);
    }
    t2 = Environment.TickCount;
    MessageBox.Show("Dynamic delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());
    Func<int, int> funcL = Execute;
    t1 = Environment.TickCount;
    for (int j = 0; j < 100000000; j++)
    {
        i = funcL(j);
    }
    t2 = Environment.TickCount;
    MessageBox.Show("Delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());
}

委托在 C# 中出现意外性能不佳

正如 Hans 在对您的问题的评论中提到的那样,Execute 方法非常简单,几乎可以肯定它是由您的"本机"测试中的抖动内联

的。

因此,您看到的不是标准方法调用和委托调用之间的比较,而是内联i * 2操作和委托调用之间的比较。(i * 2操作可能归结为单个机器指令,大约是你能得到的一样快。

使Execute方法稍微复杂一些,以防止内联(和/或使用MethodImplOptions.NoInlining编译器提示(;然后,您将在标准方法调用和委托调用之间进行更真实的比较。在大多数情况下,差异可以忽略不计:

[MethodImpl(MethodImplOptions.NoInlining)]
static int Execute(int i) { return ((i / 63.53) == 34.23) ? -1 : (i * 2); }
public static volatile int Result;
private static void Main(string[] args)
{
    const int iterations = 100000000;
    {
        Result = Execute(42);  // pre-jit
        var s = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            Result = Execute(i);
        }
        s.Stop();
        Console.WriteLine("Native: " + s.ElapsedMilliseconds);
    }
    {
        Func<int, int> func;
        using (var cscp = new CSharpCodeProvider())
        {
            var cp = new CompilerParameters { GenerateInMemory = true, CompilerOptions = @"/optimize" };
            string src = @"public static class Foo { public static int Execute(int i) { return ((i / 63.53) == 34.23) ? -1 : (i * 2); } }";
            var cr = cscp.CompileAssemblyFromSource(cp, src);
            var mi = cr.CompiledAssembly.GetType("Foo").GetMethod("Execute");
            func = (Func<int, int>)Delegate.CreateDelegate(typeof(Func<int, int>), mi);
        }
        Result = func(42);  // pre-jit
        var s = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            Result = func(i);
        }
        s.Stop();
        Console.WriteLine("Dynamic delegate: " + s.ElapsedMilliseconds);
    }
    {
        Func<int, int> func = Execute;
        Result = func(42);  // pre-jit
        var s = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            Result = func(i);
        }
        s.Stop();
        Console.WriteLine("Delegate: " + s.ElapsedMilliseconds);
    }
}
这是

有道理的。委托不是函数指针。它们意味着类型检查,安全性和许多其他东西。它们更接近虚拟函数调用的速度(见这篇文章(,即使性能影响来自完全不同的东西。

为了更好地比较不同的调用技术(其中一些在问题中没有提到(,请阅读这篇文章。