ExpressionTree,Emit,反射
https://www.cnblogs.com/7tiny/p/9861166.html
【前言】 前几日心血来潮想研究着作一个Spring框架,天然地就涉及到了Ioc容器对象建立的问题,研究怎么高性能地建立一个对象。第一联想到了Emit,兴致冲冲写了个Emit建立对象的工厂。在作性能测试的时候,发现竟然比反射Activator.CreateInstance方法建立对象毫无优点可言。继而又写了个Expression Tree的对象工厂,发现和Emit不相上下,比起系统反射方法仍然无优点可言。html
第一时间查看了园内大神们的研究,例如:git
Leven 的 探究.net对象的建立,质疑《再谈Activator.CreateInstance(Type type)方法建立对象和Expression Tree建立对象性能的比较》github
Will Meng 的 再谈Activator.CreateInstance(Type type)方法建立对象和Expression Tree建立对象性能的比较(更新版)数组
详细对比了后发现,上述大佬们的对比都是使用无参构造函数作的性能对比。缓存
因而,我也用Expression Tree写了个无参构造函数的Demo,对比之下发现,无参构造Expression Tree实现方式确实比反射Activator.CreateInstance方法性能要高不少,可是若是想要兼容带参的对象建立,在参数判断,方法缓存上来讲,耗费了不少的时间,性能并不比直接反射调用好,下面放出测试的代码,欢迎博友探讨,雅正。框架
【实现功能】 咱们要实现一个建立对象的工厂。函数
new对象,Expression Tree实现(参数/不考虑参数),Emit+Delegate(考虑参数)实现方式作对比。性能
【实现过程】 准备好测试的对象:单元测试
准备两个类,ClassA,ClassB,其中ClassA有ClassB的参数构造,ClassB无参构造。测试
复制代码 1 public class ClassA 2 { 3 public ClassA(ClassB classB) { } 4 public int GetInt() => default(int); 5 } 6 public class ClassB 7 { 8 public int GetInt() => default(int); 9 } 复制代码 1.最简单不考虑参数的 Expression Tree方式建立对象(无带参构造函数) 复制代码 1 public class ExpressionCreateObject 2 { 3 private static Func<object> func; 4 public static T CreateInstance<T>() where T : class 5 { 6 if (func == null) 7 { 8 var newExpression = Expression.New(typeof(T)); 9 func = Expression.Lambda<Func<object>>(newExpression).Compile(); 10 } 11 return func() as T; 12 } 13 } 复制代码 2.有参数处理的Expression Tree方式建立对象(带参构造函数,且针对参数的委托进行了本地缓存) 复制代码 1 public class ExpressionCreateObjectFactory 2 { 3 private static Dictionary<string, Func<object[], object>> funcDic = new Dictionary<string, Func<object[], object>>(); 4 public static T CreateInstance<T>() where T : class 5 { 6 return CreateInstance(typeof(T), null) as T; 7 } 8 9 public static T CreateInstance<T>(params object[] parameters) where T : class 10 { 11 return CreateInstance(typeof(T), parameters) as T; 12 } 13 14 static Expression[] buildParameters(Type[] parameterTypes, ParameterExpression paramExp) 15 { 16 List<Expression> list = new List<Expression>(); 17 for (int i = 0; i < parameterTypes.Length; i++) 18 { 19 //从参数表达式(参数是:object[])中取出参数 20 var arg = BinaryExpression.ArrayIndex(paramExp, Expression.Constant(i)); 21 //把参数转化成指定类型 22 var argCast = Expression.Convert(arg, parameterTypes[i]); 23 24 list.Add(argCast); 25 } 26 return list.ToArray(); 27 } 28 29 public static object CreateInstance(Type instanceType, params object[] parameters) 30 { 31 32 Type[] ptypes = new Type[0]; 33 string key = instanceType.FullName; 34 35 if (parameters != null && parameters.Any()) 36 { 37 ptypes = parameters.Select(t => t.GetType()).ToArray(); 38 key = string.Concat(key, "_", string.Concat(ptypes.Select(t => t.Name))); 39 } 40 41 if (!funcDic.ContainsKey(key)) 42 { 43 ConstructorInfo constructorInfo = instanceType.GetConstructor(ptypes); 44 45 //建立lambda表达式的参数 46 var lambdaParam = Expression.Parameter(typeof(object[]), "args"); 47 48 //建立构造函数的参数表达式数组 49 var constructorParam = buildParameters(ptypes, lambdaParam); 50 51 var newExpression = Expression.New(constructorInfo, constructorParam); 52 53 funcDic.Add(key, Expression.Lambda<Func<object[], object>>(newExpression, lambdaParam).Compile()); 54 } 55 return funcDickey; 56 } 57 } 复制代码 3.有参数处理的 Emit+Delegate 方式建立对象(带参构造函数,且针对参数Delegate本地缓存) 复制代码 1 namespace SevenTiny.Bantina 2 { 3 internal delegate object CreateInstanceHandler(object[] parameters); 4 5 public class CreateObjectFactory 6 { 7 static Dictionary<string, CreateInstanceHandler> mHandlers = new Dictionary<string, CreateInstanceHandler>(); 8 9 public static T CreateInstance<T>() where T : class 10 { 11 return CreateInstance<T>(null); 12 } 13 14 public static T CreateInstance<T>(params object[] parameters) where T : class 15 { 16 return (T)CreateInstance(typeof(T), parameters); 17 } 18 19 public static object CreateInstance(Type instanceType, params object[] parameters) 20 { 21 Type[] ptypes = new Type[0]; 22 string key = instanceType.FullName; 23 24 if (parameters != null && parameters.Any()) 25 { 26 ptypes = parameters.Select(t => t.GetType()).ToArray(); 27 key = string.Concat(key, "", string.Concat(ptypes.Select(t => t.Name))); 28 } 29 30 if (!mHandlers.ContainsKey(key)) 31 { 32 CreateHandler(instanceType, key, ptypes); 33 } 34 return mHandlerskey; 35 } 36 37 static void CreateHandler(Type objtype, string key, Type[] ptypes) 38 { 39 lock (typeof(CreateObjectFactory)) 40 { 41 if (!mHandlers.ContainsKey(key)) 42 { 43 DynamicMethod dm = new DynamicMethod(key, typeof(object), new Type[] { typeof(object[]) }, typeof(CreateObjectFactory).Module); 44 ILGenerator il = dm.GetILGenerator(); 45 ConstructorInfo cons = objtype.GetConstructor(ptypes); 46 47 if (cons == null) 48 { 49 throw new MissingMethodException("The constructor for the corresponding parameter was not found"); 50 } 51 52 il.Emit(OpCodes.Nop); 53 54 for (int i = 0; i < ptypes.Length; i++) 55 { 56 il.Emit(OpCodes.Ldarg_0); 57 il.Emit(OpCodes.Ldc_I4, i); 58 il.Emit(OpCodes.Ldelem_Ref); 59 if (ptypes[i].IsValueType) 60 il.Emit(OpCodes.Unbox_Any, ptypes[i]); 61 else 62 il.Emit(OpCodes.Castclass, ptypes[i]); 63 } 64 65 il.Emit(OpCodes.Newobj, cons); 66 il.Emit(OpCodes.Ret); 67 CreateInstanceHandler ci = (CreateInstanceHandler)dm.CreateDelegate(typeof(CreateInstanceHandler)); 68 mHandlers.Add(key, ci); 69 } 70 } 71 } 72 } 73 } 复制代码 【系统测试】 咱们编写单元测试代码对上述几个代码段进行性能测试:
1.无参构造函数的单元测试 复制代码 1 [Theory] 2 [InlineData(1000000)] 3 [Trait("description", "无参构造各方法调用性能对比")] 4 public void PerformanceReportWithNoArguments(int count) 5 { 6 Trace.WriteLine($"#{count} 次调用:"); 7 8 double time = StopwatchHelper.Caculate(count, () => 9 { 10 ClassB b = new ClassB(); 11 }).TotalMilliseconds; 12 Trace.WriteLine($"‘New’耗时 {time} milliseconds"); 13 14 double time2 = StopwatchHelper.Caculate(count, () => 15 { 16 ClassB b = CreateObjectFactory.CreateInstance<ClassB>(); 17 }).TotalMilliseconds; 18 Trace.WriteLine($"‘Emit 工厂’耗时 {time2} milliseconds"); 19 20 double time3 = StopwatchHelper.Caculate(count, () => 21 { 22 ClassB b = ExpressionCreateObject.CreateInstance<ClassB>(); 23 }).TotalMilliseconds; 24 Trace.WriteLine($"‘Expression’耗时 {time3} milliseconds"); 25 26 double time4 = StopwatchHelper.Caculate(count, () => 27 { 28 ClassB b = ExpressionCreateObjectFactory.CreateInstance<ClassB>(); 29 }).TotalMilliseconds; 30 Trace.WriteLine($"‘Expression 工厂’耗时 {time4} milliseconds"); 31 32 double time5 = StopwatchHelper.Caculate(count, () => 33 { 34 ClassB b = Activator.CreateInstance<ClassB>(); 35 //ClassB b = Activator.CreateInstance(typeof(ClassB)) as ClassB; 36 }).TotalMilliseconds; 37 Trace.WriteLine($"‘Activator.CreateInstance’耗时 {time5} milliseconds"); 38 39 40 /** 41 #1000000 次调用: 42 ‘New’耗时 21.7474 milliseconds 43 ‘Emit 工厂’耗时 174.088 milliseconds 44 ‘Expression’耗时 42.9405 milliseconds 45 ‘Expression 工厂’耗时 162.548 milliseconds 46 ‘Activator.CreateInstance’耗时 67.3712 milliseconds 47 * */ 48 } 复制代码
经过上面代码测试能够看出,100万次调用,相比直接New对象,Expression无参数考虑的实现方式性能最高,比系统反射Activator.CreateInstance的方法性能要高。
这里没有提供Emit无参的方式实现,看这个性能测试的结果,预估Emit无参的实现方式性能会比系统反射的性能要高的。
2.带参构造函数的单元测试 复制代码 1 [Theory] 2 [InlineData(1000000)] 3 [Trait("description", "带参构造各方法调用性能对比")] 4 public void PerformanceReportWithArguments(int count) 5 { 6 Trace.WriteLine($"#{count} 次调用:"); 7 8 double time = StopwatchHelper.Caculate(count, () => 9 { 10 ClassA a = new ClassA(new ClassB()); 11 }).TotalMilliseconds; 12 Trace.WriteLine($"‘New’耗时 {time} milliseconds"); 13 14 double time2 = StopwatchHelper.Caculate(count, () => 15 { 16 ClassA a = CreateObjectFactory.CreateInstance<ClassA>(new ClassB()); 17 }).TotalMilliseconds; 18 Trace.WriteLine($"‘Emit 工厂’耗时 {time2} milliseconds"); 19 20 double time4 = StopwatchHelper.Caculate(count, () => 21 { 22 ClassA a = ExpressionCreateObjectFactory.CreateInstance<ClassA>(new ClassB()); 23 }).TotalMilliseconds; 24 Trace.WriteLine($"‘Expression 工厂’耗时 {time4} milliseconds"); 25 26 double time5 = StopwatchHelper.Caculate(count, () => 27 { 28 ClassA a = Activator.CreateInstance(typeof(ClassA), new ClassB()) as ClassA; 29 }).TotalMilliseconds; 30 Trace.WriteLine($"‘Activator.CreateInstance’耗时 {time5} milliseconds"); 31 32 33 /** 34 #1000000 次调用: 35 ‘New’耗时 29.3612 milliseconds 36 ‘Emit 工厂’耗时 634.2714 milliseconds 37 ‘Expression 工厂’耗时 620.2489 milliseconds 38 ‘Activator.CreateInstance’耗时 588.0409 milliseconds 39 * */ 40 } 复制代码
经过上面代码测试能够看出,100万次调用,相比直接New对象,系统反射Activator.CreateInstance的方法性能最高,而Emit实现和ExpressionTree的实现方法就要逊色一筹。
【总结】 经过本文的测试,对反射建立对象的性能有了从新的认识,在.netframework低版本中,反射的性能是没有如今这么高的,可是通过微软的迭代升级,目前最新版本的反射调用性能仍是比较客观的,尤为是突出在了针对带参数构造函数的对象建立上,有机会对内部实现作详细分析。
无参构造不管是采用Expression Tree缓存委托仍是Emit直接实现,都无需额外的判断,也并未使用反射,性能比系统反射要高是能够预见到的。可是加入了各类参数的判断以及针对不一样参数的实现方式的缓存以后,性能却被反射反超,由于参数的判断以及缓存时Key的生成,Map集合的存储键值判断等都是有耗时的,综合下来,并不比反射好。
系统的性能瓶颈每每并不在反射或者不反射这些建立对象方法的损耗上,通过测试能够发现,即使使用反射建立,百万次的调用耗时也不到1s,可是百万次的系统调用每每耗时是比较长的,咱们作测试的目的仅仅是为了探索,具体在框架的实现中,会着重考虑框架的易用性,容错性等更为关键的部分。
声明:并非对园内大佬有啥质疑,我的认为仅仅是对以往测试的一种测试用例的补充,若是对测试过程有任何异议或者优化的部分,欢迎评论区激起波涛~!~~
【源码地址】 本文源代码地址:https://github.com/sevenTiny/SevenTiny.Bantina/blob/master/10-Code/Test.SevenTiny.Bantina/CreateObjectFactoryTest.cs
或者直接clone代码查看项目:https://github.com/sevenTiny/SevenTiny.Bantina
【版权声明】 本文为七小站主原创做品,转载请注明出处:http://www.cnblogs.com/7tiny/ 且在文章页面明显位置给出原文连接。