再看ExpressionTree,Emit,反射创建对象性能对比
【前言】
前几日心血来潮想研究着做一个Spring框架,自然地就涉及到了Ioc容器对象创建的问题,研究怎么高性能地创建一个对象。第一联想到了Emit,兴致冲冲写了个Emit创建对象的工厂。在做性能测试的时候,发现居然比反射Activator.CreateInstance方法创建对象毫无优势可言。继而又写了个Expression Tree的对象工厂,发现和Emit不相上下,比起系统反射方法仍然无优势可言。
第一时间查看了园内大神们的研究,例如:
Leven 的 探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》
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 funcDic[key](parameters);
- 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 mHandlers[key](parameters);
- 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,但是百万次的系统调用往往耗时是比较长的,我们做测试的目的仅仅是为了探索,具体在框架的实现中,会着重考虑框架的易用性,容错性等更为关键的部分。
声明:并不是对园内大佬有啥质疑,个人认为仅仅是对以往测试的一种测试用例的补充,如果对测试过程有任何异议或者优化的部分,欢迎评论区激起波涛~!~~
【源码地址】
或者直接clone代码查看项目:https://github.com/sevenTiny/SevenTiny.Bantina