【前言】

  前几日心血来潮想研究着做一个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. [十六]JavaIO之InputStreamReader 与 OutputStreamWriter

      简介 InputStreamReader OutputStreamWriter是转换流 InputStreamReader 是字节流通向字符流的桥梁,它将字节流转换为字符流. OutputStre ...

  2. 痞子衡嵌入式:PCM编码与Waveform音频文件(.wav)格式详解

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是PCM编码及Waveform音频文件格式. 嵌入式里有时候也会和音频打交道,比如最近特别火的智能音箱产品,离不开前端的音频信号采集.降噪 ...

  3. 不要使用Resource Owner Password Credentials

    不要使用Resource Owner Password Credentials 文章链接在这里 前言 最近公司项目在做一些重构,因为公司多个业务系统各自实现了一套登录逻辑,比较混乱.所以,现在需要做一 ...

  4. Log4Net使用学习笔记

    项目源文件下载https://files.cnblogs.com/files/ckym/Log4NetTestSourceCode.zip Log4net是一款非常好用的日志记录的框架,使用它可以实现 ...

  5. MVC+angularjs

    angularjs可以解决开发人员不擅长HTML的问题,采用模块化配置,但是不支持样式的微调和修改 angularjs+MVC开发的协同办公平台,贴下图 编辑页面+附件 列表页 一个页面涉及另一个子表 ...

  6. 结合JDK源码看设计模式——建造者模式

    概念: 将一个复杂对象的构建与它的表示分离.使得同样构建过程可以创建不同表示适用场景: 一个对象有很多属性的情况下 想把复杂的对象创建和使用分离 优点: 封装性好,扩展性好 详解: 工厂模式注重把这个 ...

  7. nodejs cookie与session

    cookie.session cookie:在浏览器保存一些数据,每次请求都会带过来 *不安全.有限(4K) session:保存数据,保存在服务端 *安全.无限 ------------------ ...

  8. Android为TV端助力 MediaPlayer 错误代码(error code)总结 转载

    public static final int MEDIA_ERROR_IO Added in API level 17 File or network related operation error ...

  9. Android EditText常用属性

    一.EditText介绍 ①EditText是一个输入框,在Android开发中是常用的控件.也是获取用户数据的一种方式. ②EditText是TextView的子类,它继承了TextView的所有属 ...

  10. asp.net core参数保护之自定义要保护的参数类型

    asp.net core参数保护之自定义要保护的参数类型 Intro 为了实现 asp.net core 下的参数保护,扩展了asp.net core 中 DataProtection,可以自动化的保 ...