ExpressionTree,Emit,反射

https://www.cnblogs.com/7tiny/p/9861166.html

【前言】
  前几日心血来潮想研究着做一个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 func;
4 public static T CreateInstance() where T : class
5 {
6 if (func == null)
7 {
8 var newExpression = Expression.New(typeof(T));
9 func = Expression.Lambda<Func>(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() where T : class
5 {
6 return CreateInstance(typeof(T), null) as T;
7 }
8
9 public static T CreateInstance(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 list = new List();
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() where T : class
10 {
11 return CreateInstance(null);
12 }
13
14 public static T CreateInstance(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();
17 }).TotalMilliseconds;
18 Trace.WriteLine($"‘Emit 工厂’耗时 {time2} milliseconds");
19
20 double time3 = StopwatchHelper.Caculate(count, () =>
21 {
22 ClassB b = ExpressionCreateObject.CreateInstance();
23 }).TotalMilliseconds;
24 Trace.WriteLine($"‘Expression’耗时 {time3} milliseconds");
25
26 double time4 = StopwatchHelper.Caculate(count, () =>
27 {
28 ClassB b = ExpressionCreateObjectFactory.CreateInstance();
29 }).TotalMilliseconds;
30 Trace.WriteLine($"‘Expression 工厂’耗时 {time4} milliseconds");
31
32 double time5 = StopwatchHelper.Caculate(count, () =>
33 {
34 ClassB b = Activator.CreateInstance();
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(new ClassB());
17 }).TotalMilliseconds;
18 Trace.WriteLine($"‘Emit 工厂’耗时 {time2} milliseconds");
19
20 double time4 = StopwatchHelper.Caculate(count, () =>
21 {
22 ClassA a = ExpressionCreateObjectFactory.CreateInstance(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/ 且在文章页面明显位置给出原文链接。

ExpressionTree,Emit,反射的更多相关文章

  1. C#利用Emit反射实现AOP,以及平台化框架封装思路

    C#利用Emit反射实现AOP,以及平台化框架封装思路 这是前两天扒的一段动态代理AOP代码,用的Emit反射生成子类来实现代理模式,在这里做个小笔记,然后讨论一下AOP框架的实现思路. 首先是主函数 ...

  2. ExpressionTree——让反射性能向硬编码看齐

    缘起 最近又换了工作.然后开心是以后又能比较频繁的关注博客园了.办离职手续的这一个月梳理了下近一年自己写的东西,然后就有了此文以及附带的代码. 反射 关于反射,窃以为,他只是比较慢.在这个前提下,个人 ...

  3. 什么是Emit,什么是反射,二者区别到底是什么?(转)

    Emit的准确定义,我们看看微软给出的答案 System.Reflection.Emit 命名空间包含{ 允许编译器或工具发出元数据和发出 Microsoft 中间语言 (MSIL) ,并可选择在磁盘 ...

  4. C# 使用Emit实现动态AOP框架 (一)

    目  录 C# 使用Emit实现动态AOP框架 (一) C# 使用Emit实现动态AOP框架 (二) C# 使用Emit实现动态AOP框架 (三) C# 使用Emit实现动态AOP框架 进阶篇之异常处 ...

  5. Dapper.NET——轻量ORM

    Dapper.NET使用 http://www.cnblogs.com/yankliu-vip/p/4182892.html 本文目录 Dapper.NET使用 1.为什么选择Dapper 2.以Da ...

  6. 搭建一套自己实用的.net架构(3)【ORM-Dapper+DapperExtensions】

    现在成熟的ORM比比皆是,这里只介绍Dapper的使用(最起码我在使用它,已经运用到项目中,小伙伴们反馈还可以). 优点: 1.开源.轻量.小巧.上手容易. 2.支持的数据库还蛮多的, Mysql,S ...

  7. Dapper.Net 应用

    Dapper应用 1.Dapper是什么 Dapper是一款轻量级ORM工具.如果你在小的项目中,使用Entity Framework.NHibernate 来处理大数据访问及关系映射,未免有点杀鸡用 ...

  8. Dapper

    前一段做一个技术分享关于dapper的,现在再总结一下,也好长时间没有更新博客了--,用到的东西 Dapper.AutoFac .AutoMapper.FluentValidation: 下面说一下D ...

  9. 分享自己的超轻量级高性能ORM数据访问框架Deft

    Deft 简介 Deft是一个超轻量级高性能O/R mapping数据访问框架,简单易用,几分钟即可上手. Deft包含如下但不限于此的特点: 1.按照Transact-SQL的语法语义风格来设计,只 ...

  10. Dapper学习笔记(1)-开始

    Dapper是一款开源的轻量级ORM工具,源代码下载地址为https://github.com/StackExchange/dapper-dot-net,其具有以下特点: 1.Dapper是一个轻型的 ...

随机推荐

  1. Linux系统服务管理 服务管理

    Linux独立服务管理 启动服务 systemctl start 服务名称.service 设置开机自启动 systemctl enable 服务名称.service 停止开机自启动 systemct ...

  2. po dto vo bo

    DozerBeanMapper是JavaBean的映射工具,可以进行对象之间相同属性名赋值     关于PO.DTO.VO在分层模型之间的关系:首先在持久层由DAO访问数据库将数据对象封装成PO,然后 ...

  3. 给JSP应用提供JSTL支持(IntelliJ IDEA)

    ===========手动分割线===2018-12-26============================================= Maven项目直接添加如下依赖即可: <de ...

  4. CSS3自定义发光radiobox单选框

    在线演示 本地下载

  5. Qt性能问题

    使用Qt库开发通信上位机软件,如串口.CAN总线等,涉及到接收界面高速刷新,会使CPU消耗率过高(20%以上),可能还会卡顿. 具体原因不知道,突然想放弃Qt了 ps: 1.问题出在界面刷新,会占据C ...

  6. Linux系统下chkconfig命令使用详解

    chkconfig命令可以用来检查.设置系统的各种服务 使用语法:chkconfig [--add][--del][--list][系统服务] 或 chkconfig [--level <等级代 ...

  7. codeforces 357

    C 题意: ###n个勇士编号1-n,m个回合对战,每个回合由仍留在游戏里的编号Li~Ri的人参加,胜者为Xi,输的人退出游戏. ###求一个a1-an的序列,若ai为胜者,则ai=0,否则ai=打败 ...

  8. HDU 1176 免费馅饼 简单动态规划

    世道很简单的动态规划,但是却错了,让我很无语,改来改去还是不对,第二天有写就对了,之后我就耐着性子慢慢比较之前的错误代码,发现 第一次错:纯粹用了a[i][j]+=max3(a[i+1][j-1], ...

  9. html checkbox多选框语法与结构

    <input name="Fruit" type="checkbox" value="" /> 用法用例 <foreach ...

  10. LeetCode第[19]题(Java):Remove Nth Node From End of List(删除链表的倒数第N个节点)

    题目:删除链表的倒数第N个节点 难度:Medium 题目内容: Given a linked list, remove the n-th node from the end of list and r ...