【前言】

  前几日心血来潮想研究着做一个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无参构造。

 public class ClassA
{
public ClassA(ClassB classB) { }
public int GetInt() => default(int);
}
public class ClassB
{
public int GetInt() => default(int);
}

  1.最简单不考虑参数的 Expression Tree方式创建对象(无带参构造函数)

     public class ExpressionCreateObject
{
private static Func<object> func;
public static T CreateInstance<T>() where T : class
{
if (func == null)
{
var newExpression = Expression.New(typeof(T));
func = Expression.Lambda<Func<object>>(newExpression).Compile();
}
return func() as T;
}
}

  2.有参数处理的Expression Tree方式创建对象(带参构造函数,且针对参数的委托进行了本地缓存)

     public class ExpressionCreateObjectFactory
{
private static Dictionary<string, Func<object[], object>> funcDic = new Dictionary<string, Func<object[], object>>();
public static T CreateInstance<T>() where T : class
{
return CreateInstance(typeof(T), null) as T;
} public static T CreateInstance<T>(params object[] parameters) where T : class
{
return CreateInstance(typeof(T), parameters) as T;
} static Expression[] buildParameters(Type[] parameterTypes, ParameterExpression paramExp)
{
List<Expression> list = new List<Expression>();
for (int i = ; i < parameterTypes.Length; i++)
{
//从参数表达式(参数是:object[])中取出参数
var arg = BinaryExpression.ArrayIndex(paramExp, Expression.Constant(i));
//把参数转化成指定类型
var argCast = Expression.Convert(arg, parameterTypes[i]); list.Add(argCast);
}
return list.ToArray();
} public static object CreateInstance(Type instanceType, params object[] parameters)
{ Type[] ptypes = new Type[];
string key = instanceType.FullName; if (parameters != null && parameters.Any())
{
ptypes = parameters.Select(t => t.GetType()).ToArray();
key = string.Concat(key, "_", string.Concat(ptypes.Select(t => t.Name)));
} if (!funcDic.ContainsKey(key))
{
ConstructorInfo constructorInfo = instanceType.GetConstructor(ptypes); //创建lambda表达式的参数
var lambdaParam = Expression.Parameter(typeof(object[]), "_args"); //创建构造函数的参数表达式数组
var constructorParam = buildParameters(ptypes, lambdaParam); var newExpression = Expression.New(constructorInfo, constructorParam); funcDic.Add(key, Expression.Lambda<Func<object[], object>>(newExpression, lambdaParam).Compile());
}
return funcDic[key](parameters);
}
}

  3.有参数处理的 Emit+Delegate 方式创建对象(带参构造函数,且针对参数Delegate本地缓存)

 namespace SevenTiny.Bantina
{
internal delegate object CreateInstanceHandler(object[] parameters); public class CreateObjectFactory
{
static Dictionary<string, CreateInstanceHandler> mHandlers = new Dictionary<string, CreateInstanceHandler>(); public static T CreateInstance<T>() where T : class
{
return CreateInstance<T>(null);
} public static T CreateInstance<T>(params object[] parameters) where T : class
{
return (T)CreateInstance(typeof(T), parameters);
} public static object CreateInstance(Type instanceType, params object[] parameters)
{
Type[] ptypes = new Type[];
string key = instanceType.FullName; if (parameters != null && parameters.Any())
{
ptypes = parameters.Select(t => t.GetType()).ToArray();
key = string.Concat(key, "_", string.Concat(ptypes.Select(t => t.Name)));
} if (!mHandlers.ContainsKey(key))
{
CreateHandler(instanceType, key, ptypes);
}
return mHandlers[key](parameters);
} static void CreateHandler(Type objtype, string key, Type[] ptypes)
{
lock (typeof(CreateObjectFactory))
{
if (!mHandlers.ContainsKey(key))
{
DynamicMethod dm = new DynamicMethod(key, typeof(object), new Type[] { typeof(object[]) }, typeof(CreateObjectFactory).Module);
ILGenerator il = dm.GetILGenerator();
ConstructorInfo cons = objtype.GetConstructor(ptypes); if (cons == null)
{
throw new MissingMethodException("The constructor for the corresponding parameter was not found");
} il.Emit(OpCodes.Nop); for (int i = ; i < ptypes.Length; i++)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldelem_Ref);
if (ptypes[i].IsValueType)
il.Emit(OpCodes.Unbox_Any, ptypes[i]);
else
il.Emit(OpCodes.Castclass, ptypes[i]);
} il.Emit(OpCodes.Newobj, cons);
il.Emit(OpCodes.Ret);
CreateInstanceHandler ci = (CreateInstanceHandler)dm.CreateDelegate(typeof(CreateInstanceHandler));
mHandlers.Add(key, ci);
}
}
}
}
}

【系统测试】

  我们编写单元测试代码对上述几个代码段进行性能测试:

  1.无参构造函数的单元测试

 [Theory]
[InlineData()]
[Trait("description", "无参构造各方法调用性能对比")]
public void PerformanceReportWithNoArguments(int count)
{
Trace.WriteLine($"#{count} 次调用:"); double time = StopwatchHelper.Caculate(count, () =>
{
ClassB b = new ClassB();
}).TotalMilliseconds;
Trace.WriteLine($"‘New’耗时 {time} milliseconds"); double time2 = StopwatchHelper.Caculate(count, () =>
{
ClassB b = CreateObjectFactory.CreateInstance<ClassB>();
}).TotalMilliseconds;
Trace.WriteLine($"‘Emit 工厂’耗时 {time2} milliseconds"); double time3 = StopwatchHelper.Caculate(count, () =>
{
ClassB b = ExpressionCreateObject.CreateInstance<ClassB>();
}).TotalMilliseconds;
Trace.WriteLine($"‘Expression’耗时 {time3} milliseconds"); double time4 = StopwatchHelper.Caculate(count, () =>
{
ClassB b = ExpressionCreateObjectFactory.CreateInstance<ClassB>();
}).TotalMilliseconds;
Trace.WriteLine($"‘Expression 工厂’耗时 {time4} milliseconds"); double time5 = StopwatchHelper.Caculate(count, () =>
{
ClassB b = Activator.CreateInstance<ClassB>();
//ClassB b = Activator.CreateInstance(typeof(ClassB)) as ClassB;
}).TotalMilliseconds;
Trace.WriteLine($"‘Activator.CreateInstance’耗时 {time5} milliseconds"); /**
#1000000 次调用:
‘New’耗时 21.7474 milliseconds
‘Emit 工厂’耗时 174.088 milliseconds
‘Expression’耗时 42.9405 milliseconds
‘Expression 工厂’耗时 162.548 milliseconds
‘Activator.CreateInstance’耗时 67.3712 milliseconds
* */
}

  

  通过上面代码测试可以看出,100万次调用,相比直接New对象,Expression无参数考虑的实现方式性能最高,比系统反射Activator.CreateInstance的方法性能要高。

  这里没有提供Emit无参的方式实现,看这个性能测试的结果,预估Emit无参的实现方式性能会比系统反射的性能要高的。

  2.带参构造函数的单元测试

 [Theory]
[InlineData()]
[Trait("description", "带参构造各方法调用性能对比")]
public void PerformanceReportWithArguments(int count)
{
Trace.WriteLine($"#{count} 次调用:"); double time = StopwatchHelper.Caculate(count, () =>
{
ClassA a = new ClassA(new ClassB());
}).TotalMilliseconds;
Trace.WriteLine($"‘New’耗时 {time} milliseconds"); double time2 = StopwatchHelper.Caculate(count, () =>
{
ClassA a = CreateObjectFactory.CreateInstance<ClassA>(new ClassB());
}).TotalMilliseconds;
Trace.WriteLine($"‘Emit 工厂’耗时 {time2} milliseconds"); double time4 = StopwatchHelper.Caculate(count, () =>
{
ClassA a = ExpressionCreateObjectFactory.CreateInstance<ClassA>(new ClassB());
}).TotalMilliseconds;
Trace.WriteLine($"‘Expression 工厂’耗时 {time4} milliseconds"); double time5 = StopwatchHelper.Caculate(count, () =>
{
ClassA a = Activator.CreateInstance(typeof(ClassA), new ClassB()) as ClassA;
}).TotalMilliseconds;
Trace.WriteLine($"‘Activator.CreateInstance’耗时 {time5} milliseconds"); /**
#1000000 次调用:
‘New’耗时 29.3612 milliseconds
‘Emit 工厂’耗时 634.2714 milliseconds
‘Expression 工厂’耗时 620.2489 milliseconds
‘Activator.CreateInstance’耗时 588.0409 milliseconds
* */
}

  

  通过上面代码测试可以看出,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

  

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

  1. 实际项目中,看 ECharts 和 HighCharts 渲染性能对比,表面看衣装,本质看内功!!!

    最近做项目,使用的是echarts显示图表数据,但是数据量比较多的时候,有卡顿的情况.后来同事拿echarts和HighCharts做了对比,仅供大家参考.同时感谢同事做的工作. 一.查询1天的源数据 ...

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

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

  3. Java的几种创建实例方法的性能对比

    近来打算自己封装一个比较方便读写的Office Excel 工具类,前面已经写了一些,比较粗糙本就计划重构一下,刚好公司的电商APP后台原有的导出Excel实现出现了可怕的性能问题,600行的数据生成 ...

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

    本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephisto)写的,SourceLink 阅读目录 介绍 环境搭建 测试用例 MSDN说明 ...

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

    原文:SQL点滴10-使用with语句来写一个稍微复杂sql语句,附加和子查询的性能对比 今天偶尔看到sql中也有with关键字,好歹也写了几年的sql语句,居然第一次接触,无知啊.看了一位博主的文章 ...

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

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

  7. 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时,突发奇想,想把最近主流的编程语言性能作个简单的比较, 至于怎么比,还是不 ...

  8. MyISAM与InnoDB两者之间区别与选择,详细总结,性能对比

    1.MyISAM:默认表类型,它是基于传统的ISAM类型,ISAM是Indexed Sequential Access Method (有索引的顺序访问方法) 的缩写,它是存储记录和文件的标准方法.不 ...

  9. 【转】HashMap,ArrayMap,SparseArray源码分析及性能对比

    HashMap,ArrayMap,SparseArray源码分析及性能对比 jjlanbupt 关注 2016.06.03 20:19* 字数 2165 阅读 7967评论 13喜欢 43 Array ...

随机推荐

  1. Spring Boot(十四)RabbitMQ延迟队列

    一.前言 延迟队列的使用场景:1.未按时支付的订单,30分钟过期之后取消订单:2.给活跃度比较低的用户间隔N天之后推送消息,提高活跃度:3.过1分钟给新注册会员的用户,发送注册邮件等. 实现延迟队列的 ...

  2. ABP框架 将EntityFrameworkCore生成的SQL语句输出到控制台

    首先 在 EntityFrameworkCore中安装 Microsoft.Extensions.Logging.Console nuget install Microsoft.Extensions. ...

  3. Maven项目POM文件错误,提示“Plugin execution not covered by lifecycle configuration”的解决方案

    一. 问题 Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-depend ...

  4. 多线程(4)Task

    使用线程池使得创建线程已经很简单了,但是使用线程池不支持线程的取消,完成和失败通知等交互操作,为了解决这些问题,.net 4.0带来了TPL(Task Parallel Library)任务并行库,下 ...

  5. AEAI HR薪资汇总功能介绍

    1 概述 人力资源系统是一个公司重要的管理工具,而薪资管理是人力资源管理系统中最为核心的功能模块,它包括不同员工的薪资标准.薪资的组成部分,例如:对奖惩管理.保险和年假等员工必备的福利待遇进行统一管理 ...

  6. iOS-----------设置自定义字体

    1.将字体加入到项目中 2.在info.plist文件中加入相应信息,这一步实际上实在项目的Info页里面增加Fonts provided by application项,并设置相应的ttf文件进去, ...

  7. C语言使用HZK16显示每个像素的代码

    下边内容段是关于C语言使用HZK16显示每个像素的内容. #include<stdio.h>#include<stdlib.h>void main(){ int i,j; ch ...

  8. 基于WanAndroid开放API实现的文章阅读APP

    简介 基于WanAndroid开放API开发的技术文章阅读App.主要功能包括:首页.体系.项目.公众号.搜索.登录.收藏.夜间模式等. 用到的第三方框架 RxJava RxAndroid Retro ...

  9. 通过Visual Studio 2012 比较SQL Server 数据库的架构变更

    一 需求 随着公司业务的发展,数据库实例也逐渐增多,数据库也会越来越多,有时候我们会发现正式生产数据库也测试数据库数据不一致,也有可能是预发布环境下的数据库与其他数据库架构不一致,或者,分布式数据库上 ...

  10. Java 集合系列(一)

    Java集合系列文章将以思维导图为主要形式来展示知识点,让零碎的知识形成体系. 这篇文章主要介绍的是[Java 集合的基本知识],即Java 集合简介. 毕业出来一直使用 PHP 进行开发,对于大学所 ...