再看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,但是百万次的系统调用往往耗时是比较长的,我们做测试的目的仅仅是为了探索,具体在框架的实现中,会着重考虑框架的易用性,容错性等更为关键的部分。

  声明:并不是对园内大佬有啥质疑,个人认为仅仅是对以往测试的一种测试用例的补充,如果对测试过程有任何异议或者优化的部分,欢迎评论区激起波涛~!~~

【源码地址】

  本文源代码地址:https://github.com/sevenTiny/SevenTiny.Bantina/blob/master/10-Code/Test.SevenTiny.Bantina/CreateObjectFactoryTest.cs

  或者直接clone代码查看项目:https://github.com/sevenTiny/SevenTiny.Bantina

  

原文地址:https://www.cnblogs.com/7tiny/p/9861166.html

时间: 2024-10-21 17:13:14

再看ExpressionTree,Emit,反射创建对象性能对比的相关文章

使用httpclient实现http链接池与使用HttpURLConnection发送http请求的方法与性能对比

使用httpclient实现http链接池与使用HttpURLConnection发送http请求的方法与性能对比 在项目中需要使用http调用接口,实现了两套发送http请求的方法,一个是使用apache的httpclient提供的http链接池来发送http请求,另一个是使用java原生的HttpURLConnection来发送http请求,并对两者性能进行了对比. 使用httpclient中的链接池发送http请求 使用最新的4.5.2版httpclient进行实现.在maven中引入 <

Python web开发:几个模板系统的性能对比(转)

http://blog.chedushi.com/archives/910 结论: 点评一下吧.django就是个渣,不多废话了.webpy的代码很简洁,可惜速度太慢了.bottle看起来快一点,不过也没有多出彩.tornado本身速度很快,不过模板--也就是如此吧.真的值得一用的,只有jinja2,mako,cheetah三个.速度都小于了5ms,单核每秒可以生成200个页面,16核机器上大概就能跑到3000req/s,性能比较高.jinja2的速度比较折衷,配置灵活,语法类似django是他

[1]Nginx_lua的应用及性能对比

对于Web高性能服务器上的选择,这个是很多人头痛的问题.对于Apache.lighttpd.Nginx都用他们优点,在什么情况下我们如何去选择适合自己的Web高性能服务器,如何去搭建一个适合自己的架构环境,这个是一个很麻烦的事情.接下来,在ADC 2012(Alibaba Developer Conference 2012)大会上,51CTO记者有幸采访到了一淘数据平台与产品部技术专家--清无(花名),为我们解读Nginx_lua的一些优势及劣势,以及在高性能服务器上的选择. AD: 对于Web

Tomcat 7优化前及优化后的性能对比(转载)

一.运行环境 CPU: Intel(R) Pentium(R) [email protected]  : 内存:4G,装的是32位win7,只认出3G,没有花时间去整ramdisk之类的东西: 操作系统:win7 32位: JDK:1.7.0_55 Tomcat:7.0.53 大家不要笑,公司电脑,就给配这样的,慢的要死,悲剧! 下面所有测试都是基于1000个请求做的,且都是访问Tomcat默认的ROOT首页 二.未调优前 并发用户数从10-1000挨个测试,测试结果如下: 从上面的测试结果来看

【重磅干货】看了此文,Oracle SQL优化文章不必再看!

听“俊”一席话,胜读十年书.看了这篇由DBA+社群联合发起人丁俊大师(网名:dingjun123)分享的SQL优化大作,其他Oracle SQL优化文章都不必再看了! 专家简介 丁俊 网名:dingjun123 DBA+社群联合发起人 性能优化专家,Oracle ACEA,ITPUB开发版资深版主.8年电信行业从业经验,在某大型电信系统提供商工作7年,任资深工程师,从事过系统开发与维护.业务架构和数据分析.系统优化等工作.擅长基于ORACLE的系统优化,精通SQL.PL/SQL.JAVA等.电子

2017年的golang、python、php、c++、c、java、Nodejs性能对比(golang python php c++ java Nodejs Performance)

2017年的golang.python.php.c++.c.java.Nodejs性能对比 本人在PHP/C++/Go/Py时,突发奇想,想把最近主流的编程语言性能作个简单的比较, 至于怎么比,还是不得不用神奇的斐波那契算法.可能是比较常用或好玩吧. 好了,talk is cheap, show me your code!  打开Mac,点开Clion开始Coding吧! 1.怎么第一是Go呢,因为我个人最近正在用,感觉很不错 package main import "fmt" fun

转载:内存拷贝MEMCPY()与VMSPLICE()性能对比

内存拷贝MEMCPY()与VMSPLICE()性能对比 综述 在上一篇文章<进程间大数据拷贝方法调研>中介绍和对比了三种A进程读取文件然后拷贝给B进程的方法,测试结果显示在涉及到内存与磁盘间的数据传输时,splice方法由于避免了内核缓冲区与用户缓冲区之间的多次数据拷贝,表现最好.但是由于这种对比限定在包含I/O读写,且进程不能对数据进行修改的特殊情景中,毕竟在实际情况下不太常见,理论意义大于实际意义. 那本文要探讨的情景,在实际编程过程中就十分常见了: A进程的内存中有一大块数据,要传递给B

不同Framework下StringBuilder和String的性能对比,及不同Framework性能比(附Demo)

本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephisto)写的,SourceLink 阅读目录 介绍 环境搭建 测试用例 MSDN说明 我的理解 Demo下载 本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephisto)写的,SourceLink 介绍   自己对String和StringBuilder的处理机制略懂,大胆的设想下两者的性能对比会出现什么样的令人意外的

SQL点滴10—使用with语句来写一个稍微复杂sql语句,附加和子查询的性能对比

原文:SQL点滴10-使用with语句来写一个稍微复杂sql语句,附加和子查询的性能对比 今天偶尔看到sql中也有with关键字,好歹也写了几年的sql语句,居然第一次接触,无知啊.看了一位博主的文章,自己添加了一些内容,做了简单的总结,这个语句还是第一次见到,学习了.我从简单到复杂地写,希望高手们不要见笑.下面的sql语句设计到三个表,表的内容我用txt文件复制进去,这里不妨使用上一个随笔介绍的建立端到端的package的方法将这些表导入到数据库中,具体的就不说了. 从这里下载文件employ