完美的.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 = ; 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 = ; 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 = ; 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 = ; 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泛型对象时偷了个懒,直接利用编译器加上一句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 = ; 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 = ; 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 = ; 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 = ; 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 = ; 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 = ; 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 = ; 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 = ; 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里改善性能
有更理想方法的同学可以留言讨论一下
完美的.net泛型也有特定的性能黑点?追根问底并且改善这个性能问题的更多相关文章
- 泛型 "new的性能"
完美的.net泛型也有特定的性能黑点?追根问底并且改善这个性能问题 完美的.net真泛型真的完美吗 码C#多年,不求甚解觉得泛型就是传说中那么完美,性能也是超级好,不错,在绝大部分场景下泛型表现简直可 ...
- 《C#高级编程(第六版)》泛型学习笔记(一):泛型优点和特性 (转载)
原文出处:http://www.cnblogs.com/xun126/archive/2011/01/13/1933838.html 泛型是CLR 2.0的一个新特性,在CLR 1.0中,要创建一个灵 ...
- java 27 - 7 反射之 通过反射越过泛型检查
之前学过的集合里面都有泛型,规定了泛型的类型以后,就不能往这个集合添加除了这个类型之外的类型数据了. 那么,有什么方法可以越过这个泛型,添加特定类型以外的类型数据么? 例子: 往ArrayList& ...
- C# 泛型简介
摘要:本文讨论泛型处理的问题空间.它们的实现方式.该编程模型的好处,以及独特的创新(例如,约束.一般方法和委托以及一般继承).此外,本文还讨论 .NET Framework 如何利用泛型. 下载 Ge ...
- c#2解决c#1中的问题之用泛型实现参数化类型
为什么需要泛型 你手中还有c#1的代码吗?数一数其中的强制转换有多少,特别是那些大量使用集合的代码.几乎每次使用foreach都需要隐式的强制转换.使用那些为不同数据类型而设计的类型,就意味着强制转换 ...
- C#高级编程:泛型优点和特性
泛型是CLR 2.0的一个新特性,在CLR 1.0中,要创建一个灵活的类或方法,但该类或方法在编译期间不知道使用什么类,就得以Object类为基础.而Object在编译期间没有类型安全性,因此必须进行 ...
- C#学习笔记(十八):数据结构和泛型
数据结构 只有这四种 a.集合:数据之间没有特定的关系 b.线性结构:数据之间有一对一的前后联系 c.树形结构:数据之间有一对多的关系,一个父节点有多个子节点,一个子节点只能有一个父节点 d.图状结构 ...
- .net中的泛型全面解析
从2.0起我们一直就在谈论泛型,那么什么是泛型,泛型有什么好处,与泛型相关的概念又该怎么使用,比如泛型方法,泛型委托.这一篇我会全面的介绍泛型. 那么首先我们必须搞清楚什么是泛型,泛型其实也是一种类型 ...
- golang拾遗:为什么我们需要泛型
从golang诞生起是否应该添加泛型支持就是一个热度未曾消减的议题.泛型的支持者们认为没有泛型的语言是不完整的,而泛型的反对者们则认为接口足以取代泛型,增加泛型只会徒增语言的复杂度.双方各执己见,争执 ...
随机推荐
- php--opp--1.什么是面向对象?
面向对象编程(Object Oriented Programming, OOP, 面向对象程序设计)是一种计算机编程架构,OOP的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成 ...
- mysql cluster 名词概念解读
Node Group [number_of_node_groups] = number_of_data_nodes / NoOfReplicas Partition When using ndbd, ...
- 第三百二十九天 how can I 坚持
今天莫名其妙的烦,,都是上午搞电脑搞的,好乱,心情很差,又感觉有那么多事. 希望周六不要加班啊.很烦,不想加班. 貌似事情也不是很多.但是为什么会感觉乱七八糟的呢,力不从心的感觉,是能力不行吗. 晚上 ...
- jxse2.6在jdk8下,JxtaMulticastSocket存在的问题
JxtaMulticastSocket覆写了java.net.MulticastSocket的bind方法: @Override public void bind(SocketAddress addr ...
- Android实例-实现扫描二维码并生成二维码(XE8+小米5)
相关资料: 第三方资料太大没法写在博文上,请下载CSDN的程序包. 程序包下载: http://download.csdn.net/detail/zhujianqiangqq/9657186 注意事项 ...
- HDU 4612 Warm up(2013多校2 1002 双连通分量)
Warm up Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others)Total Su ...
- 5540 asa 8.4 防火墙
配置等级策略,保证outside端口可以访问inside端口 access-list 100 extended permit icmp any any access-list 100 extended ...
- oracle学习 一 (持续更新中)
首先你需要创建一个表空间,然后,再创建一个用户名,用户名要给他指定一个表空间,并且给这个用户赋予权限, DBA: 拥有全部特权,是系统最高权限,只有DBA才可以创建数据库结构. RESOURCE:拥有 ...
- yii缓存设置使用
'filecache'=>array( 'class'=>'system.caching.CFileCache', 'directoryLevel'=>'3',), //在main. ...
- 全代码实现ios-3
决定做ios开发的时候,看了很多本关于ios开发的书籍,其中有国内的人写的,也有根据外国的书翻译过来的. 很可惜,这些书里的例子没办法照搬过来,因为Xcode更新换代太快了,而这些书本的内容更新的速度 ...