无法弄清楚为什么此DynamicMethod比本地c#方法要慢得多. [英] Can't figure out why this DynamicMethod so much slower than the native c# method.

查看:119
本文介绍了无法弄清楚为什么此DynamicMethod比本地c#方法要慢得多.的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写了一个简单的对象复印机,用于复制公共属性.
我不知道为什么Dynamic方法比C#版本慢很多.


持续时间
C#方法:4,963毫秒
动态方法:19,924毫秒


完整的(控制台程序)代码:

I wrote a simple object copier that copies public properties.
I can''t figure out why the Dynamic method is a lot slower than the c# version.


Durations
C# method : 4,963 ms
Dynamic method : 19,924 ms


Full (console program) code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace DuckCopy.SpeedTests
{
    class Program
    {
        const int NBRECORDS = 100 * 1000 * 1000;

        public class Person
        {
            private int mSomeNumber;

            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime DateOfBirth { get; set; }
            public int SomeNumber
            {
                get { return mSomeNumber; }
                set { mSomeNumber = value; }
            }
        }

        public static Action<T1, T2> CreateCopier<T1, T2>()
        {
            var meth = new DynamicMethod("copy", null, new Type[] { typeof(T1), typeof(T2) }, restrictedSkipVisibility: true);
            ILGenerator il = meth.GetILGenerator();
            int cpt = 0;

            var stopHere = typeof(Program).GetMethod("StopHere");

            foreach (var mi1 in typeof(T1).GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                var mi2 = typeof(T2).GetProperty(mi1.Name, BindingFlags.Public | BindingFlags.Instance);
                if (mi1 != null && mi2 != null)
                {
                    cpt++;
                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_1);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Callvirt, mi1.GetMethod);
                    il.Emit(OpCodes.Callvirt, mi2.SetMethod);
                }
            }
            il.Emit(OpCodes.Ret);

            var dlg = meth.CreateDelegate(typeof(Action<T1, T2>));
            return (Action<T1, T2>)dlg;
        }

        static void Main(string[] args)
        {
            var person1 = new Person() { FirstName = "Pascal", LastName = "Ganaye", DateOfBirth = new DateTime(1909, 5, 1), SomeNumber = 23456 };
            var person2 = new Person();

            var copyUsingAMethod = (Action<Person, Person>)CopyPerson;
            var copyUsingADynamicMethod = CreateCopier<Person, Person>();

            copyUsingAMethod(person1, person2); // 4882 ms
            var sw = Stopwatch.StartNew();
            for (int i = 0; i < NBRECORDS; i++)
            {
                copyUsingAMethod(person1, person2);
            }
            Console.WriteLine("{0} ms", sw.ElapsedMilliseconds);

            copyUsingADynamicMethod(person1, person2); // 19920 ms
            sw = Stopwatch.StartNew();
            for (int i = 0; i < NBRECORDS; i++)
            {
                copyUsingADynamicMethod(person1, person2); 
            }
            Console.WriteLine("{0} ms", sw.ElapsedMilliseconds);


            Console.ReadKey(intercept: true);
        }

        private static void CopyPerson(Person person1, Person person2)
        {
            person2.FirstName = person1.FirstName;
            person2.LastName = person1.LastName;
            person2.DateOfBirth = person1.DateOfBirth;
            person2.SomeNumber = person1.SomeNumber;
        }
    }
}



根据我的调试,这两种方法具有相同的IL代码.



From what I can debug the two methods have the same IL code.

IL_0000: nop        
IL_0001: ldarg.1    
IL_0002: ldarg.0    
IL_0003: callvirt   System.String get_FirstName()/DuckCopy.SpeedTests.Program+Person
IL_0008: callvirt   Void set_FirstName(System.String)/DuckCopy.SpeedTests.Program+Person
IL_000d: nop        
IL_000e: ldarg.1    
IL_000f: ldarg.0    
IL_0010: callvirt   System.String get_LastName()/DuckCopy.SpeedTests.Program+Person
IL_0015: callvirt   Void set_LastName(System.String)/DuckCopy.SpeedTests.Program+Person
IL_001a: nop        
IL_001b: ldarg.1    
IL_001c: ldarg.0    
IL_001d: callvirt   System.DateTime get_DateOfBirth()/DuckCopy.SpeedTests.Program+Person
IL_0022: callvirt   Void set_DateOfBirth(System.DateTime)/DuckCopy.SpeedTests.Program+Person
IL_0027: nop        
IL_0028: ldarg.1    
IL_0029: ldarg.0    
IL_002a: callvirt   Int32 get_SomeNumber()/DuckCopy.SpeedTests.Program+Person
IL_002f: callvirt   Void set_SomeNumber(Int32)/DuckCopy.SpeedTests.Program+Person
IL_0034: nop        
IL_0035: ret   

推荐答案

很难准确说明计时结果为何如您所见它们,但是我可以看到一个问题:您错误地对方法的调用计时,因为您没有考虑到JIT花费的时间.同样,这两种方法最终都是用JIT解释的,因此很难解释为什么结果如此不同,但是在所有情况下,都应首先从方程式中排除JIT.

可以保守地假设,在第一次调用方法之前,尚未对方法进行JIT编译.就是说,只有在第二次调用方法时,才应该结束秒表.在最简单的方法中,您可以重复执行两次所有计时的方法的调用-通常,您会看到第二轮的计时结果是不同的.

我不确定它会给您带来更多可解释的结果,可能还有其他一些问题,但这是您首先可以做的事情.如果您能报告结果,我会很好.

根据我的所有观察,使用动态方法非常有效.如果MSIL代码通过System.Reflection.Emit编写得很好,并且方法的生成(它本身的运行速度很慢)是否可以正确地重用并执行一次.

—SA
Hard to say exactly why the timing results are as you observe them, but I can see one problem: you time the calls of the method incorrectly, because you don''t take into account the time taken by JIT. Again, both methods are eventually JIT-interpreted, so it''s pretty hard to explain why the results are so different, but in all cases, you should first exclude JIT from equation.

You can assume, most conservatively, that a method is not yet JIT-compiled before it is called for them very first time. That said, you should wind up your stopwatch only when a method is called for the second time. In the most simplistic approach, you can just repeat the call of the method which performs all the timing twice — usually, you can see that the results of timing on the second run are different.

I''m not sure it will give you more explainable results, as could be some other problems, but this is something you can do first. I would be good it you report back your results.

In all my observations, using Dynamic Methods is very efficient. If the MSIL code is written well via the System.Reflection.Emit, and if the generation of methods (which is itself pretty slow) is properly reused and performed once.

—SA


我稍加修改了您的代码,以便可以使用所有.NET版本进行编译.

使用.NET2和3.5,标准方法和动态方法之间的执行时间没有差异,在释放模式下两者都大约需要1400-1500ms.

但是,.NET 4(v4.0.30319)完全不同.结果与您的结果相似,标准方法为1400,动态方法为19700ms.

我修改后的方法(其中注释了加速修复)是:
I modified your code slightly so that it could be compiled using all .NET versions.

With .NET2 and 3.5 there was no difference in execution time between the standard and dynamic methods, both taking about 1400 - 1500ms in release mode.

However .NET 4 (v4.0.30319) was a different story altogether. The results were similar to yours with 1400 for the standard and 19700ms for the dynamic method.

My modified method, with the speedup fix commented out, is:
public static Action<T1, T2> CreateCopier<T1, T2>() {
  // SLOW
  DynamicMethod meth = new DynamicMethod(
    "copy",
    null,
    new Type[] { typeof(T1), typeof(T2) },
    true);
  // FAST
  //DynamicMethod meth = new DynamicMethod(
  //  "copy",
  //  null,
  //  new Type[] { typeof(T1), typeof(T2) },
  //  typeof(Program),                  // associate with a type
  //  true);
  ILGenerator il = meth.GetILGenerator();
  int cpt = 0;

  foreach (PropertyInfo mi1 in typeof(T1).GetProperties(BindingFlags.Public | BindingFlags.Instance)) {
    PropertyInfo mi2 = typeof(T2).GetProperty(mi1.Name, BindingFlags.Public | BindingFlags.Instance);
    if (mi1 != null && mi2 != null) {
      cpt++;
      il.Emit(OpCodes.Nop);
      il.Emit(OpCodes.Ldarg_1);
      il.Emit(OpCodes.Ldarg_0);
      il.Emit(OpCodes.Callvirt, mi1.GetGetMethod());
      il.Emit(OpCodes.Callvirt, mi2.GetSetMethod());
    }
  }
  il.Emit(OpCodes.Ret);

  Delegate dlg = meth.CreateDelegate(typeof(Action<T1, T2>));
  return (Action<T1, T2>)dlg;
}



通过使用DynamicMethod(string, Type, Type[], bool)构造函数将动态方法与匿名程序集关联时,会导致执行时间大大降低.我猜想.NET 4比以前的版本进行了更多的安全检查,尽管我不了解实际发生的情况或对其进行解释.

使用DynamicMethod(string, Type, Type[], Type, bool)构造函数将方法与Type关联可以完全消除速度损失.

关于MSDN的一些说明可能是相关的(如果只有我能理解它们的话!)
http://msdn.microsoft.com/en-us/library/bb348332 (v = vs.100).aspx [ http://msdn.microsoft.com/en-us/library/9syytdak.aspx [ ^ ]

艾伦.



The big slow down in execution time is caused when the dynamic method is associated with an anonymous assembly by using the DynamicMethod(string, Type, Type[], bool) constructor. I would guess that .NET 4 is doing more security checks than the previous versions, although I have no insight into, or explanation for, what is actually going on.

Associating the method with a Type by using the DynamicMethod(string, Type, Type[], Type, bool) constructor completely removes the speed penalty.

There are some notes on MSDN which may be relevant (if only I could understand them!)
http://msdn.microsoft.com/en-us/library/bb348332(v=vs.100).aspx[^]
http://msdn.microsoft.com/en-us/library/9syytdak.aspx[^]

Alan.


这篇关于无法弄清楚为什么此DynamicMethod比本地c#方法要慢得多.的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆