10

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

Note that - as I run the dynamic method before starting the stopwatch - the duration do not include the compilation phase. I run that in Debug and Release mode, in x86 and x64 mode, and from VS and from the command line with roughly the same result (dynamic method is 400% slower).

        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.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;
        }

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

I applogize if you read this twice. I posted this originally in: http://www.codeproject.com/Answers/494714/Can-27tplusfigureplusoutpluswhyplusthisplusDynamic but did not get all the answers I hoped.

edited 17 nov 2012 15:11:

removed the nop
removed the extra ="" which came from I don't where.
4

2 回答 2

14

这有点晚了,但是如果您在.NET 4中为所有程序集设置一些安全属性并使用内置委托类型(或具有相同安全属性的委托),您将看到相当大的性能提升。

以下是您需要的属性:

[assembly: AllowPartiallyTrustedCallers]
[assembly: SecurityTransparent]
[assembly: SecurityRules(SecurityRuleSet.Level2,SkipVerificationInFullTrust=true)]

这实际上似乎是一个小错误。但是因为你说你的代码不会提高安全权限,你不会阻止部分信任的调用者,所以如果你skipVisibility=true完全信任使用,调用Func<int,int>委托基本上应该避免几乎所有的权限检查。

还有一件事,因为这些是委托,如果您将它们视为实例方法,您将获得最佳性能,即使它们不是。也就是说,始终使用Delegate.CreateDelegate接受firstArgument参数的两种方法之一,并将初始对象引用添加到您的委托。

考虑构建DynamicMethodwith skipVisibility=true,但不指定所有者。分配所有者允许您运行无法验证的代码。你可以用这个做一些非常糟糕的事情,所以我会避免它,除非你知道你在做什么。

于 2013-01-12T19:27:36.173 回答
11

.NET这个问题是由Framework 4.0中所做的更改引入的。我在 CodeProject 上找到了用户“ Alan-N ”发布的解决方案。

DynamicMethod当获取与“系统提供的、完全受信任的、安全透明的程序集”相关联时,会导致执行时间大幅减慢,如果您使用DynamicMethod(string, Type, Type[], bool)构造函数,就会发生这种情况。.NET 4 似乎比以前的版本进行了更多的安全检查,尽管我对实际发生的事情没有洞察力或解释。

DynamicMethod与 a相关联Type(通过使用DynamicMethod(string, Type, Type[], Type, bool)构造函数;注意附加的Type-valued 参数'owner')完全消除了速度损失。

MSDN上有一些可能相关的注释(如果我能理解它们!):

于 2012-11-17T15:34:57.050 回答