泛型 "new的性能"
完美的.net泛型也有特定的性能黑点?追根问底并且改善这个性能问题
完美的.net真泛型真的完美吗
码C#多年,不求甚解觉得泛型就是传说中那么完美,性能也是超级好,不错,在绝大部分场景下泛型表现简直可以用完美来形容,不过随着前一阵重做IOC时,才发现与自己预想中不一样,觉得自己还是图样图森破,太过拿衣服了
在前面一篇文章(一步一步造个IoC轮子(二),详解泛型工厂)中,我说了泛型工厂带来"接近new的性能",是错误的,我要道歉,其实是完全达不到直接new的性能,差了两个数量级,当然还是比反射速度强很多很多很多
性能黑点出在哪里?
我来来演示一下普通类型和泛型的实际测试吧
先来做两个类,一个普通一个泛型
public class NormalClass
{ }
public class GenericClass<T>
{ }
再来写个循环测试
var sw = new Stopwatch();
Console.WriteLine("请输入循环次数");
int max = int.Parse(Console.ReadLine()); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = new NormalClass();
}
sw.Stop();
Console.WriteLine("直接创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = new GenericClass<int>();
}
sw.Stop();
Console.WriteLine("泛型创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); Console.ReadLine();
好了,E3CPU,dotnet core 1.0 Release下测试结果(本篇全部测试结果均是E3CPU,dotnet core 1.0 Release模式下测试)
一千万次循环
直接创建耗时3ms,平均每次0.3ns
泛型创建耗时3ms,平均每次0.3ns
表现简单完美啊,顺便一提.net core速度提高了很多,像这样的测试如果在.net 2.0-4.6直接new简单对象一千万次下表现都是30-50ms左右,.net core这个真是提升了一个数量级.
那么我说的性能黑点在哪里了?
问题就在于像泛型工厂这样的代码中,在泛型方法里new 泛型对象,我们继续来段代码测试一下
public static ISMS Create()
{
return new XSMS();
} public static ISMS Create<T>() where T : class, ISMS, new()
{
return new T();
} public static void Main(string[] args)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var sw = new Stopwatch();
Console.WriteLine("请输入循环次数");
int max = int.Parse(Console.ReadLine()); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = Create();
}
sw.Stop();
Console.WriteLine("直接创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = Create<XSMS>();
}
sw.Stop();
Console.WriteLine("泛型方法创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); Console.ReadLine();
}
以上代码,如果泛型真表现是像我们所预期那么完美,两个测试的时间应该是基本相等才对,那来我们来实际测试一下看吧
一千万次结果
直接创建耗时3ms,平均每次0.3ns
泛型方法创建耗时619ms,平均每次61.9ns
WTF,差异为什么这么大,这是什么回事,200倍啊,传说中泛型不是几乎没有性能损失的么
考虑到这么简单的代码,身经百码的我是不可能写错的,难道这个是泛型实现的问题?看看实际编译出什么鬼再说吧
我们打开一下ILSPY看看IL代码是什么样的,这东西比ildasm用着方便,毕竟我是懒人
原来.net实现这个泛型方法new泛型对象jf 偷了个懒,直接利用编译器加上一句System.Activator.CreateInstance<T>()的方法完事,这个打破了我一直美好的幻想,我以为泛型真的表现得像模板一样完美,JIT时才完全膨胀代码,都是不求甚解导致我的曲解
追根问底,我们再来把new泛型放到泛型内部看看编译后的IL
噢NO,跟泛型方法一样,System.Activator.CreateInstance<T>(),至此我们可以得出结论,new泛型对象都是编译器利用System.Activator.CreateInstance<T>()来做的
性能也就降到跟System.Activator.CreateInstance<T>()的水平了
改善性能黑点
虽然Activator.CreateInstance已经很快了,但本着钻研的精神,我们来尝试加速一下这个创建,至少在泛型中的创建性能,最直接的方法当然是模拟编译后IL代码里直接new普通对象的方法了,怎么处理呢,造一个方法,调用这个方法返回要创建的对象
上代码再说吧
public class FastActivator<T> where T : class, new()
{
private static readonly Func<T> createFunc = BuildFunc();
private static Func<T> BuildFunc()
{
var newMethod = new DynamicMethod("CreateFunc", typeof(T), Type.EmptyTypes, true);
var il = newMethod.GetILGenerator();
il.DeclareLocal(typeof(T));
il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret); return newMethod.CreateDelegate(typeof(Func<T>)) as Func<T>;
}
public static T CreateInstance()
{
return createFunc();
}
}
在上面的代码中,我们创建一个FastActivator<T>的类,T的约束为class而且有空的构造器方法
static readonly Func<T>这里当访问到这个类时就调用BuildFunc的方法,还记得前面提到的static readonly魔法吗,仅仅调用一次,线程安全
CreateInstance()方法里返回createFunc创建的对象
对于IL代码不了解的同学,我来简单解释一下这段IL Emit的代码吧
var newMethod = new DynamicMethod("CreateFunc", typeof(T), Type.EmptyTypes, true); //<-创建一个DynamicMethod 动态方法
var il = newMethod.GetILGenerator();//<-取出ILGenerator对象
il.DeclareLocal(typeof(T));//<-接一来定义一个临时本地变量,类型为T
----------------------------------分隔一下----------------------------------------------------
接下来到IL最核心的代码构建了,如下代码
il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
OpCodes.Newobj是调用构造方法 等同代码里的new关键字,后面 typeof(T).GetConstructor(Type.EmptyTypes)是取出T的空构造方法,整句IL代码意思等于代码 new XX类型() ,这里的XX是T实际的类型
il.Emit(OpCodes.Stloc_0);
OpCodes.Stloc从MSDN里的解释是:“从计算堆栈的顶部弹出当前值并将其存储到指定索引处的局部变量列表中。”,意思是把值存入局部变量,Stloc_0中的0就是第0个变量,即我们刚才在上面定义的那个变量
il.Emit(OpCodes.Ldloc_0);
OpCodes.Ldloc从MSDN里的解释是:“将指定索引处的局部变量加载到计算堆栈上。”,意思是把变量加载到栈上,这里是把索引为0的变量加入栈,在IL代码里基本上都是把参数结果等对加载到栈上做相应操作,写IL代码是脑中要有一个栈的表,临时调用的数据都是存到栈上,然后调用方法时就会把栈的参数一一传给方法,当然这个我说不清楚,加深了解直接用ILSPY和代码相互参照就是了
il.Emit(OpCodes.Ret);
OpCodes.Ret就是最后一步就是返回了等同代码里的Return,即使void类型的方法最后一样也是有个OpCodes.Ret表示当前方法完成并返回,如果栈上有值当然就相当于Return xx了
在上面的代码里new出来的对象(指针引用)先存在了栈顶部,然后我们又取出来存入变量[0]然后又从变量[0]取出来压入栈再返回,是否就表示我直接new了就return也行呢
不错,真的行,把il.Emit(OpCodes.Stloc_0);il.Emit(OpCodes.Ldloc_0);这两句及变量声明il.DeclareLocal(typeof(T));去掉实测完全没有影响,我不知编译器为何都要加上这两句,是不够智能还是兼容,不清楚,反正IL代码执行相当快,加上去掉这两句千万次调用基本上时间表现是一致的
最后一个是newMethod.CreateDelegate(typeof(Func<T>)) as Func<T>;是利用方法创建一个泛型委托,让我们可以直接调用委托而不用反射来调用方法
好了,代码准备好了,是驴是马拉出来溜一溜就知道了
测试代码如下
var sw = new Stopwatch();
Console.WriteLine("请输入循环次数");
int max = int.Parse(Console.ReadLine()); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = Create();
}
sw.Stop();
Console.WriteLine("直接创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = Create<XSMS>();
}
sw.Stop();
Console.WriteLine("泛型方法创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = Activator.CreateInstance<XSMS>();
}
sw.Stop();
Console.WriteLine("Activator创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = FastCreate<XSMS>();
}
sw.Stop();
Console.WriteLine("FastActivator创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max);
Console.ReadLine();
测试结果
还是一千万次
直接创建耗时3ms,平均每次0.3ns
泛型方法创建耗时582ms,平均每次58.2ns
Activator创建耗时552ms,平均每次55.2ns
FastActivator创建耗时130ms,平均每次13ns
虽然比Activator快了近5倍,比预期直接new的速度还是差了两个数量级,当然在.net2.0-4.6里是一个数量级,WTF究竟慢在哪里了
好吧,参考泛型工厂里,我们用个静态的代理对象,代理对象里面包含个Create方法来创建需要的对象来试试能不能再快点,直接上代码吧
internal class FastActivatorModuleBuilder
{
public static readonly ModuleBuilder ModuleBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("DynamicFastTypeCreaterAssembly"), AssemblyBuilderAccess.Run).DefineDynamicModule("DynamicFastTypeCreaterModuleBuilder");
public static int CurrId;
}
public class FastActivator<T> where T : class, new()
{
/*//委托方法
public static readonly Func<T> createFunc = BuildFunc();
private static Func<T> BuildFunc()
{
var newMethod = new DynamicMethod("CreateFunc", typeof(T), Type.EmptyTypes, true);
var il = newMethod.GetILGenerator();
//il.DeclareLocal(typeof(T));
il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
//il.Emit(OpCodes.Stloc_0);
//il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret); return newMethod.CreateDelegate(typeof(Func<T>)) as Func<T>;
}*/
public static T CreateInstance()
{
//return createFunc();
return Creater.Create();//调用Creater对象的Create创造T对象
} private static readonly ICreater Creater = BuildCreater();
public interface ICreater
{
T Create();
}
private static ICreater BuildCreater()
{
var type = typeof(T);
var typeBuilder = FastActivatorModuleBuilder.ModuleBuilder.DefineType("FastTypeCreater_" + Interlocked.Increment(ref FastActivatorModuleBuilder.CurrId),
TypeAttributes.Class | TypeAttributes.Public, null, new Type[] { typeof(ICreater) });//创建类型,继承ICreater接口 var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);//创建类型的构造方法
var il = ctor.GetILGenerator();//从构造方法取出ILGenerator
il.Emit(OpCodes.Ret);//给构造方法加上最基本的代码(空) var createMethod = typeBuilder.DefineMethod("Create", MethodAttributes.Public | MethodAttributes.HideBySig |
MethodAttributes.NewSlot | MethodAttributes.Virtual |
MethodAttributes.Final, type, Type.EmptyTypes);//创建接口同名方法
il = createMethod.GetILGenerator();//从方法取出ILGenerator
il.DeclareLocal(type);//定义临时本地变量 il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));//调用当前新建类型的构造方法
il.Emit(OpCodes.Stloc_0);//栈入变量
il.Emit(OpCodes.Ldloc_0);//变量压栈
il.Emit(OpCodes.Ret);//返回栈顶值,方法完成 typeBuilder.DefineMethodOverride(createMethod, typeof(ICreater).GetMethod("Create"));//跟接口方法根据签名进行绑定 var createrType = typeBuilder.CreateTypeInfo().AsType();//创建类型 return (ICreater)Activator.CreateInstance(createrType);//偷懒用Activator.CreateInstance创造刚刚IL代码搞的ICreater对象,有了这个对象就可以调用对象的Create方法调用我们自己搞的IL代码了
}
}
老规矩,一千万次循环
直接创建耗时3ms,平均每次0.3ns
泛型方法创建耗时596ms,平均每次59.6ns
Activator创建耗时552ms,平均每次55.2ns
FastActivator创建耗时79ms,平均每次7.9ns
一千万次,性能继续有提升,几乎是泛型方法的8倍,算是提高了一个数量级了,实际上在 .net2.0-4.6里已经是同数量级的速度了,不过.net core狠啊,直接new够快,这里性能不如预期的原因,我想了好久,百撕不得骑姐的时候,只能够再码点基础代码来测试了
public class TestCreater
{
/// <summary>
/// 直接创建
/// </summary>
/// <returns></returns>
public static ISMS Driect()
{
return new XSMS();
}
private interface ICreater
{
ISMS Create();
}
private static readonly ICreater creater = new Creater();
private class Creater : ICreater
{
public ISMS Create()
{
return new XSMS();
}
}
/// <summary>
/// 每次都创建Creater对象用Creater对象来创建
/// </summary>
/// <returns></returns>
public static ISMS InternalCreaterCreater()
{
return new Creater().Create();
}
/// <summary>
/// 使用静态缓存的Creater创建
/// </summary>
/// <returns></returns>
public static ISMS StaticCreaterCreate()
{
return creater.Create();
}
} public static void Main(string[] args)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var sw = new Stopwatch();
Console.WriteLine("请输入循环次数");
int max = int.Parse(Console.ReadLine()); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = Create();
}
sw.Stop();
Console.WriteLine("直接创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = TestCreater.Driect();
}
sw.Stop();
Console.WriteLine("TestCreater.Driect方法创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = TestCreater.InternalCreaterCreater();
}
sw.Stop();
Console.WriteLine("TestCreater.InternalCreaterCreater方法创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); sw.Restart();
for (var i = 0; i < max; i++)
{
var x = TestCreater.StaticCreaterCreate();
}
sw.Stop();
Console.WriteLine("TestCreater.StaticCreaterCreate方法创建耗时{0}ms,平均每次{1}ns", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000M / (decimal)max); Console.ReadLine(); }
在上面的测试代码中,我造了个TestCreater的类分别来测试不同的方法,分别有直接new对象的,用ICreater代理对象来new 对象的及缓存了ICreater代理对象来new对象的
来跑一跑性能表现吧
老规矩,一千万次循环
直接创建耗时3ms,平均每次0.3ns
TestCreater.Driect方法创建耗时3ms,平均每次0.3ns
TestCreater.InternalCreaterCreater方法创建耗时3ms,平均每次0.3ns
TestCreater.StaticCreaterCreate方法创建耗时89ms,平均每次8.9ns
前面两个方法跟直接new时间完全一致,分不出什么胜负,最后一个和FastActivator吻合,性能表现完全一致,到这里我们可以得出结论了,性能下降的原因是由于引用了代理对象,毕竟要访问内在,所以这个下降也是理所当然的
优化结论
到此,泛型这个性能黑点优化算是完成了,如果要近乎直接new的性能,估计只能热更新掉运行时已经JIT过的代码,参考http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time
用这种魔法去提升微乎其微的性能,或者祈求官方在泛型里new不要偷懒在编译期实现,而是放到JIT的时候再去实现,不知会不会引起循环引用的问题
如果对于上面所有的测试代码认为有编译器优化的其实可以用ILSPY看一下IL代码或者最简单的就是在XSMS构造方法里加上计数或者控制台输出就知道这些测试代码是可靠的,没有给编译器优化忽略掉
代码就不附了,上面有,会加入前面的IOC里改善性能
有更理想方法的同学可以留言讨论一下
泛型 "new的性能"的更多相关文章
- C#泛型的性能优势
我写东西一向追求短小精悍,就不放代码去验证的,只说结论,并会与Java泛型做对比.有不对之处还望指出. 泛型作为一个在C#2.0中就引入的特性,也是C#的重要特性之一,我经常看到有人讨论泛型带来的便捷 ...
- [读书笔记]C#学习笔记四: C#2.0泛型 可控类型 匿名方法和迭代器
前言 C#1.0的委托特性使方法作为其他方法的参数来传递,而C#2.0 中提出的泛型特性则使类型可以被参数化,从而不必再为不同的类型提供特殊版本的实现方法.另外C#2.0还提出了可空类型,匿名方法和迭 ...
- 编写高质量代码改善C#程序的157个建议[泛型集合、选择集合、集合的安全]
前言 软件开发过程中,不可避免会用到集合,C#中的集合表现为数组和若干集合类.不管是数组还是集合类,它们都有各自的优缺点.如何使用好集合是我们在开发过程中必须掌握的技巧.不要小看这些技巧,一旦在开 ...
- C#学习笔记三: C#2.0泛型 可控类型 匿名方法和迭代器
前言 C#1.0的委托特性使方法作为其他方法的参数来传递,而C#2.0 中提出的泛型特性则使类型可以被参数化,从而不必再为不同的类型提供特殊版本的实现方法.另外C#2.0还提出了可空类型,匿名方法和迭 ...
- C# 深入了解泛型
本文是根据网上&书本总结来的. 1. 介绍 泛型程序设计是程序设计语言的一种风格或范式. 泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时(instantia ...
- C#程序编写高质量代码改善的157个建议【20-22】[泛型集合、选择集合、集合的安全]
建议20.使用泛型集合来替代非泛型集合 http://www.cnblogs.com/aehyok/p/3384637.html 这里有一篇文章,是我之前专门来介绍泛型的.我们应尽量的使用泛型集合.因 ...
- C#进阶之泛型(Generic)
1.泛型 泛型是framwork2.0推出的新语法,具有延迟声明的特点:把参数类型的声明推迟到调用的时候.泛型不是一个语法糖,是框架升级提供的功能.需要编辑器和JIT(just-in-time com ...
- C# 篇基础知识11——泛型和集合
.NET提供了一级功能强大的集合类,实现了多种不同类型的集合,可以根据实际用途选择恰当的集合类型. 除了数组 Array 类定义在System 命名空间中外,其他的集合类都定义在System.Coll ...
- 简单易懂的 Go 泛型使用和实现原理介绍
原文:A gentle introduction to generics in Go by Dominik Braun 万俊峰Kevin:我看了觉得文章非常简单易懂,就征求了作者同意,翻译出来给大家分 ...
随机推荐
- golang 并发之协程及通道
一.概述 在golang中,每个并发执行单元称为goroutine,当程序启动时,main函数在一个单独的goroutine中运行,(main goroutine).新的goroutine会用go语句 ...
- 前端新人学习笔记-------html/css/js基础知识点(二)
4月7日学到的知识点: 一:<img src="1.png" alt="美女"/> alt是给图片添加介绍,当图片没加载出来时,会直接显示a ...
- C# 第三方控件 下面的Item不显示了
当高版本的第三方版本 替换成低版本的第三方后,item,不显示了之后,请试着再次在这基础上添加一个Item,观察这个Item和原来已经在的却不显示的Item的区别在哪里.然后去源程序正常文件哪里 将这 ...
- Oracle的字符替换函数translate用法
参考文档如下:http://www.banping.com/2009/05/18/oracle_function_translate/ Oracle提供了一个字符替换函数translate,不同于re ...
- 全面修复IE,注册IE所有dll
全面修复IE,注册IE所有dll 复制,粘贴到文本文档里,保存成.bat文件,双击运行. rundll32.exe advpack.dll /DelNodeRunDLL32 %systemroot%\ ...
- Win7下安装Mysql方法
最近刚刚在win7系统安装了mysql客户端数据库,现整理步骤供大家学习交流! 一.下载mysql安装包 安装包名称:mysql-5.6.12-win32.zip 下载地址:http://dev.my ...
- Performance tool httperf
httperf: A relatively well-known open source utility developed by HP, for Linux operating systems on ...
- C++中的栈和队列
使用标准库的栈和队列时,先包含相关的头文件 #include<stack> #include<queue> 定义栈如下: stack<int> stk; 定义队列如 ...
- The method of using code coverage tool
Please look at the following blog: http://blog.csdn.net/superqa/article/details/9060521 Use ReportG ...
- Java的序列化
1.为啥需要序列化 在Java编程时,一个类被实例化以后,Java虚拟机使得对象处理生存状态,但是当虚拟机关闭后,对象就不复存在了,所以一个对象的生存期不会超过JVM的工作时间,那么如何才能让对象持续 ...