【前言】

  前几日心血来潮想研究着做一个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. 前端异步技术之Promise

    前言 从事前端的朋友或多或少的接触过Promise,当代码中回调函数层级过多你就会发现Promise异步编程的魅力,相信此文一定能帮你排忧解惑! Promise概念 Promise是JS异步编程中的重 ...

  2. 日志模块logging用法

    一.常用日志记录场景及最佳解决方案: 日志记录方式 最佳记录日志方案 普通情况下,在控制台显示输出 print() 报告正常程序操作过程中发生的事件 logging.info()(或者更详细的logg ...

  3. SpringMVC进行文件上传

    进行文件上传前需要添加相应的依赖 在xml文件中进行相应的文件上传解析器的配置 注意:这里有个坑,因为没注意,再排查错误的时候花了一点时间.就是给bean的id一定要是. 否者就会报如下的错误:

  4. ubuntu16.04 部署配置LVS主从

    实验环境---ubuntu16.04 四台机器:10.211.55.13—55.16 具体实验环境配置如下: 10.211.55.102  LVS_VIP 10.211.55.13  LVS_MAST ...

  5. Linux chmod命令用法

    chmod----改变一个或多个文件的存取模式(mode) Linux/Unix 的文件调用权限分为三级 : 文件拥有者.群组.其他.利用 chmod 可以藉以控制文件如何被他人所调用. 使用权限 : ...

  6. 使用bfd监控静态路由,达到网络故障及时切换功能。

    结论:通过BFD可以联动静态路由,从而监控整个网络上的网络情况,当出现故障时及时进行切换. 下面的例子,就是通过BFD监控上面的这个往返路由,当中间网络出现故障时,两端全部切换到下面的第二条路由进行通 ...

  7. ListView刷新某一项Item

    ListView现在已经很少被使用,但还是在这里列出来说一下,有时候我们仅仅需要改变listView的某个Item,如果调用adapter的notifyDataSetChanged()方法效率不高,并 ...

  8. join的简单总结

    BAT面试题:现在有T1.T2.T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉.这个多 ...

  9. UE3中的时间

    为了管理时间,Unreal将游戏运行时间片分隔为"Ticks".一个Tick是关卡中所有Actors更新的最小时间单位.一个tick一般是10ms-100ms(CPU性能越好,游戏 ...

  10. MongoDB 基本操作和聚合操作

    一 . MongoDB 基本操作 基本操作可以简单分为查询.插入.更新.删除. 1 文档查询 作用 MySQL SQL  MongoDB  所有记录  SELECT * FROM users;  db ...