【问题标题】:Run dynamically compiled C# code at native speed... how?以本机速度运行动态编译的 C# 代码......如何?
【发布时间】:2012-05-23 17:33:41
【问题描述】:

我已经阅读了几篇关于 SO 的关于编写和编译动态 C# 代码的帖子。例如,this post。我知道可以通过多种方式完成。

但是,调用代码调用程序很慢。我做了一个简单的基准测试,它比调用原生方法慢了大约 500 倍。

我想要做的是相当于加载一个 DLL 并直接(“本机”)调用它的方法之一,这将带来我想要的速度优势。

解决此问题的最简单方法是什么?将动态代码编译成dll然后加载?可以在内存中完成吗?

编辑

我不关心编译时间。仅执行。

编辑 2、3

这是我写的基准代码:

    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# 构建运行时并不比之前执行 C# 构建慢 500 倍。它们是相同的,但是需要考虑开销。你的基准是什么样的,你是使用 Reflection.Emit 还是一些编译器服务来编译?
  • Invoke 是什么东西很慢,这是我的问题的中心:如何以本机速度调用该方法?
  • 最后一点,上面的测试,当更准确的计时,实际上慢了 1100 倍。 Marc 给出的委托解决方案比原生解决方案慢 8.4 倍。

标签: c# dynamic compiler-construction assemblies


【解决方案1】:

认为值得展示所有潜在选项的外观及其性能特征。给定以下辅助类和函数:

public void Test(Func<int> func)
{        
    var watch = new Stopwatch();
    watch.Start();
    for (var i = 0; i <= 1000000; i++)
    {
        var test = func();
    }
    Console.WriteLine(watch.ElapsedMilliseconds);
}

public class FooClass { public int Execute() { return 1;}}

设置和执行:

using (Microsoft.CSharp.CSharpCodeProvider foo = 
       new Microsoft.CSharp.CSharpCodeProvider())
{
    var res = foo.CompileAssemblyFromSource(
        new System.CodeDom.Compiler.CompilerParameters() 
        {  
            GenerateInMemory = true 
        }, 
        "public class FooClass { public int Execute() { return 1;}}"
    );

    var real = new FooClass();
    Test(() => real.Execute());                   // benchmark, direct call

    var type = res.CompiledAssembly.GetType("FooClass");
    var obj = Activator.CreateInstance(type);    
    var method = type.GetMethod("Execute");
    var input = new object[] { };                
    Test(() => (int)method.Invoke(obj, input));   // reflection invoke  

    dynamic dyn = Activator.CreateInstance(type);  
    Test(() => dyn.Execute());                    // dynamic object invoke

    var action = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), null, method); 
    Test(() => action());                         // delegate
}

结果是:

8     // direct
771   // reflection invoke
41    // dynamic object invoke
7     // delegate

所以在那些你不能使用委托的情况下(如果你还不够了解?),你可以试试dynamic

【讨论】:

  • 我的测试时间不同。对于我来说,direct 是 7.8,delegate 是 43.7。
【解决方案2】:

除了 Marc 的建议之外,您还可以通过指定“优化”编译器选项来提高速度:

var res = foo.CompileAssemblyFromSource(
        new System.CodeDom.Compiler.CompilerParameters()
        {
            GenerateInMemory = true,
            CompilerOptions = "/optimize"
        },

【讨论】:

    【解决方案3】:

    是的,如果您通过MethodInfo 或非特定Delegate 调用,那么它确实会很慢。诀窍是:不要那样做。各种方法:

    • 对于个别方法,通过基本的但类型化委托,例如Action,或作为通用的包罗万象,Func&lt;object[], object&gt; - 并使用Delegate.CreateDelegate创建一个输入委托:

      Action doSomething = (Action)Delegate.CreateDelegate(typeof(Action), method);
      

      另一种变体是使用Expression API(具有.Compile() 方法)或DynamicMethod(具有CreateDelegate())。关键:你必须获得一个 typed 委托并使用 typed 调用(不是.DynamicInvoke)调用。

    • 对于生成整个类型的更复杂的情况,请考虑实现一个您知道的接口,即

      IFoo foo = (IFoo)Activator.CreateInstance(...);
      

      再次;在初始演员(非常便宜)之后,您可以使用静态代码:

      foo.Bar();
      

    不要使用someDelegate.DynamicInvoke(...)someMethod.Invoke(...),如果您追求任何类型的表现。

    【讨论】:

    • 我完全同意你的结论,但你不认为 500 大小的差异与动态调用以外的其他因素有关(例如基准测试错误)
    • @RuneFS 正在测试的方法是return i*2; - 我认为这是完全合理的预期 500 级 - 即正在测试的代码非常小,并且正在测试的只是调用速度。
    • @RuneFS 更多信息,请看这里:msmvps.com/blogs/jon_skeet/archive/2008/08/09/…
    • @IanC 具体示例:var func = (Func&lt;int, int&gt;) Delegate.CreateDelegate(typeof (Func&lt;int, int&gt;), method); ... i = func(j);
    • @IanC 8x 听起来不太可能;在大多数测试中,差异绝对是微不足道的。但是,请参阅我之前的观点:您还可以实现一个接口(在双方都可以看到的共享 dll 中声明)并使用静态绑定到该接口。键入的委托调用并不慢 - 老实说,这听起来像是一个糟糕的测试平台。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 2013-12-07
    • 2011-06-22
    • 1970-01-01
    相关资源
    最近更新 更多