【前言】

  前几日心血来潮想研究着做一个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. MySQLSource-Flume

    1. 自定义Source说明 实时监控MySQL,从MySQL中获取数据传输到HDFS或者其他存储框架,所以此时需要我们自己实现MySQLSource. 2. 自定义MySQLSource步骤 根据官 ...

  2. vue webpack打包背景图片

    vue的背景图 和 img标签图大于10KB都不会转成base64处理,可以设置limit(不推荐),所以要设置一个公共路径,解决办法如下

  3. Zabbix3.0基础教程之一:系统监控,zabbix安装与基本配置

    一.安装环境: 系统环境: 系统版本:CentOS Linux release 7.4.1708 (Core) 内核版本:3.10.0-693.el7.x86_64 关闭Firewall与SELinu ...

  4. .Net基础——程序集与CIL

    1. 程序集和CIL: 程序集是由.NET语言的编译器接受源代码文件产生的输出文件,通常分为 exe和dll两类,其中exe包含Main入口方法可以双击执行,dll则需要被其他程序集调用执行. CIL ...

  5. MySQL中 and or 查询的优先级

    这个可能是容易被忽略的问题,首选我们要清楚:MySQL中,AND的执行优先级高于OR.也就是说,在没有小括号()的限制下,总是优先执行AND语句,再执行OR语句.比如: select * from t ...

  6. Java开发笔记(五十五)关键字static的用法

    前面介绍嵌套类的时候讲到了关键字static,用static修饰类,该类就变成了嵌套类.从嵌套类的用法可知,其它地方访问嵌套类之时,无需动态创建外层类的实例,直接创建嵌套类的实例就行.其实static ...

  7. arcgis api 3.x for js 入门开发系列十一地图统计图(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  8. 从.Net到Java学习第三篇——spring boot+mybatis+mysql

    从.Net到Java学习第一篇——开篇 环境:mysql5.7 新建mysql数据库demo,然后执行如下sql脚本进行数据表创建和数据初始化: -- ------------------------ ...

  9. 探究高级的Kotlin Coroutines知识

    要说程序如何从简单走向复杂, 线程的引入必然功不可没, 当我们期望利用线程来提升程序效能的过程中, 处理线程的方式也发生了从原始时代向科技时代发生了一步一步的进化, 正如我们的Elisha大神所著文章 ...

  10. Android远程桌面助手之性能监测篇

    <Android下获取FPS的几种方法>一文中提到了Gamebench工具,它不仅可以获取FPS,还可以获取CPU及内存占用率等系统状态信息.其局限性也非常明显,切换应用时需要重新选择监控 ...