简体   繁体   English

C#中代理意外表现不佳

[英]Unexpected poor performance of delegates in C#

I posted this question earlier about dynamically compiling code in C#, and the answer has lead to another question. 我之前发布过这个关于在C#中动态编​​译代码的问题 ,答案又引出了另一个问题。

One suggestion is that I use delegates, which I tried and they work well. 一个建议是我使用代理,我尝试过并且它们运行良好。 However, they are benching at about 8.4 X slower than direct calls, which makes no sense. 然而,它们比直接呼叫慢大约8.4 X,这没有任何意义。

What is wrong with this code? 这段代码有什么问题?

My results, .Net 4.0, 64 bit, ran exe directly: 62, 514, 530 我的结果,.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());
}

As Hans mentions in the comments on your question, the Execute method is so simple that it's almost certainly being inlined by the jitter in your "native" test. 正如汉斯在关于你的问题的评论中提到的那样, Execute方法非常简单,几乎肯定会被你的“原生”测试中的抖动所强调。

So what you're seeing isn't a comparison between a standard method call and a delegate invocation, but a comparison between an inlined i * 2 operation and a delegate invocation. 所以你所看到的不是标准方法调用和委托调用之间的比较,而是内联i * 2操作和委托调用之间的比较。 (And that i * 2 operation probably boils down to just a single machine instruction, about as fast as you can get.) (并且i * 2操作可能归结为只有一个机器指令,大约可以达到最快速度。)

Make your Execute methods a bit more complicated to prevent inlining (and/or do it with the MethodImplOptions.NoInlining compiler hint); 使您的Execute方法更复杂,以防止内联(和/或使用MethodImplOptions.NoInlining编译器提示); then you'll get a more realistic comparison between standard method calls and delegate invocations. 那么你将在标准方法调用和委托调用之间进行更现实的比较。 Chances are that the difference will be negligible in most situations: 在大多数情况下,差异可以忽略不计:

[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);
    }
}

It makes sense. 这说得通。 Delegates are not function pointers. 代表不是函数指针。 They imply type checking, security and a lot of other stuffs. 它们意味着类型检查,安全性和许多其他东西。 They're more close to the speed of a virtual function call (see this post ) even if the performance impact derives from something completely different. 它们更接近于虚拟函数调用的速度(参见本文 ),即使性能影响来自完全不同的东西。

For a good comparison of different invocation techniques (some of them not mentioned in the question) read this article . 为了更好地比较不同的调用技术(其中一些没有在问题中提到),请阅读本文

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM