【前言】

  前几日心血来潮想研究着做一个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. [java]static关键字的四种用法

    在java的关键字中,static和final是两个我们必须掌握的关键字.不同于其他关键字,他们都有多种用法,而且在一定环境下使用,可以提高程序的运行性能,优化程序的结构.下面我们先来了解一下stat ...

  2. IaaS,PaaS,SaaS 的区别

    原文:http://www.ruanyifeng.com/blog/2017/07/iaas-paas-saas.html 越来越多的软件,开始采用云服务. 云服务只是一个统称,可以分成三大类. Ia ...

  3. AngularJS处理服务器端返回的JSON数据的格式问题

    用ng的$http服务发起ajax请求,php返回的JSON数据格式要正确! 一开始我的php页面是这样返回数据的: if($result){ $oid = mysqli_insert_id($con ...

  4. Scrapped or attached views may not be recycled

    在使用recycleView的时候出现了错误Scrapped or attached views may not be recycled 原因: view没有被recycled,recyclerVie ...

  5. C++ 虹软人脸识别 ArcFace 2.0 Demo

    环境配置: 开发环境:Win10 + VS 2013 SDK版本:ArcFace v2.0 OpenCV版本:2.4.9 平台配置: x64.x86下Release.Debug SDK 下载地址:戳这 ...

  6. window64位电脑如何通过VMware Workstation12.5.6安装苹果操作系统 macOS High Sierra 10.13

    1.下载 VMware-workstation-full-12.5.6.exe,macOS High Sierra 10.13.iso 2.安装 VMware-workstation时不要选择C盘,因 ...

  7. mysql安装出现问题(The service already exists)

    1.管理员身份运行cmd(系统win10) 2.输入命令cd /d F:\mysql-5.7.19-win32\bin(此为mysql要安装的目录) 3.输入安装命令mysqld install 出现 ...

  8. mysql使用索引的注意事项

    使用索引的注意事项 使用索引时,有以下一些技巧和注意事项: 1.索引不会包含有NULL值的列 只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索 ...

  9. Kafka相关内容总结(Kafka集群搭建手记)

    简介 Kafka is a distributed,partitioned,replicated commit logservice.它提供了类似于JMS的特性,但是在设计实现上完全不同,此外它并不是 ...

  10. Sqlite3-安装使用

    Sqlite安装 请访问 SQLite 下载页面,从 Windows 区下载预编译的二进制文件. 您需要下载 sqlite-tools-win32-*.zip 和 sqlite-dll-win32-* ...