上次我对C#类与结构体做了一次速度评测(http://blog.csdn.net/zyl910/article/details/6788417)。经过一段时间思索,发现还可以进一步探讨——
第一、栈变量。上次的“硬编码”,是访问类中的静态变量的。若改为访问函数中的栈变量,性能会不会有所提高?
第二、栈分配(stackalloc)。既然要测试栈变量,我们还可以顺便测试一下在栈上分配的内存块的访问性能。
第三、64位整数。由于32位系统的成功,我们已经习惯了使用32位整数(int)。现在64位系统逐渐普及,我们得为此做好准备。对于指针操作时经常要用到的偏移量增减运算来说,是使用32位整数,还是使用64位整数,或写两套代码?这需要测试后才能决定。
第四、密封类(sealed)。听说密封类能提高性能,我们可以测试一下。有两种测试方式,一是为原来的派生类增加sealed关键字,二是专门另外写一个密封类。我决定同时使用这两种方法,分别测试其性能。
一、测试代码
测试代码如下——
using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; namespace TryPointerCall { /// <summary> /// 指针操作接口 /// </summary> public interface IPointerCall { /// <summary> /// 指针操作 /// </summary> /// <param name="p">源指针</param> /// <returns>修改后指针</returns> unsafe byte* Ptr(byte* p); } #region 非泛型 /// <summary> /// [非泛型] 指针操作基类 /// </summary> public abstract class PointerCall : IPointerCall { public abstract unsafe byte* Ptr(byte* p); } /// <summary> /// [非泛型] 指针操作派生类: 指针+偏移 /// </summary> public sealed class PointerCallAdd : PointerCall { /// <summary> /// 偏移值 /// </summary> public int Offset = 0; public override unsafe byte* Ptr(byte* p) { return unchecked(p + Offset); } } /// <summary> /// [非泛型] 指针操作密封类: 指针+偏移 /// </summary> public sealed class SldPointerCallAdd : IPointerCall { /// <summary> /// 偏移值 /// </summary> public int Offset = 0; public unsafe byte* Ptr(byte* p) { return unchecked(p + Offset); } } /// <summary> /// [非泛型] 指针操作结构体: 指针+偏移 /// </summary> public struct SPointerCallAdd : IPointerCall { /// <summary> /// 偏移值 /// </summary> public int Offset; public unsafe byte* Ptr(byte* p) { return unchecked(p + Offset); } } #endregion #region 泛型 // !!! C#不支持将整数类型作为泛型约束 !!! //public abstract class GenPointerCall<T> : IPointerCall where T: int, long //{ // public abstract unsafe byte* Ptr(byte* p); // void d() // { // } //} #endregion #region 全部测试 /// <summary> /// 指针操作的一些常用函数 /// </summary> public static class PointerCallTool { #if DEBUG private const int CountLoop = 10000000; // 循环次数 #else private const int CountLoop = 200000000; // 循环次数 #endif /// <summary> /// 调用指针操作 /// </summary> /// <typeparam name="T">具有IPointerCall接口的类型。</typeparam> /// <param name="ptrcall">调用者</param> /// <param name="p">源指针</param> /// <returns>修改后指针</returns> public static unsafe byte* CallPtr<T>(T ptrcall, byte* p) where T : IPointerCall { return ptrcall.Ptr(p); } public static unsafe byte* CallClassPtr<T>(T ptrcall, byte* p) where T : PointerCall { return ptrcall.Ptr(p); } public static unsafe byte* CallRefPtr<T>(ref T ptrcall, byte* p) where T : IPointerCall { return ptrcall.Ptr(p); } // C#不允许将特定的结构体作为泛型约束。所以对于结构体只能采用上面那个方法,通过IPointerCall接口进行约束,可能会造成性能下降。 //public static unsafe byte* SCallPtr<T>(T ptrcall, byte* p) where T : SPointerCallAdd //{ // return ptrcall.Ptr(p); //} private static int TryIt_Static_Offset; private static unsafe byte* TryIt_Static_Ptr(byte* p) { return unchecked(p + TryIt_Static_Offset); } /// <summary> /// 执行测试 - 静态调用 /// </summary> /// <param name="sOut">文本输出</param> private static unsafe void TryIt_Static(StringBuilder sOut, int CountLoop) { TryIt_Static_Offset = 1; // == 性能测试 == byte* p = null; Stopwatch sw = new Stopwatch(); int i; unchecked { #region 测试 // 硬编码.栈变量 int iOffset = 1; sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = p + iOffset; } sw.Stop(); sOut.AppendLine(string.Format("硬编码.栈变量:\t{0}", sw.ElapsedMilliseconds)); // 硬编码.栈分配 int* pOffset = stackalloc int[1]; pOffset[0] = 1; sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = p + pOffset[0]; } sw.Stop(); sOut.AppendLine(string.Format("硬编码.栈分配:\t{0}", sw.ElapsedMilliseconds)); // 硬编码.静态 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = p + TryIt_Static_Offset; } sw.Stop(); sOut.AppendLine(string.Format("硬编码.静态:\t{0}", sw.ElapsedMilliseconds)); // 静态调用 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = TryIt_Static_Ptr(p); } sw.Stop(); sOut.AppendLine(string.Format("静态调用:\t{0}", sw.ElapsedMilliseconds)); #endregion // 测试 } } private static long TryIt_Static64_Offset; private static unsafe byte* TryIt_Static64_Ptr(byte* p) { return unchecked(p + TryIt_Static64_Offset); } /// <summary> /// 执行测试 - 静态调用 /// </summary> /// <param name="sOut">文本输出</param> private static unsafe void TryIt_Static64(StringBuilder sOut, int CountLoop) { TryIt_Static64_Offset = 1; // == 性能测试 == byte* p = null; Stopwatch sw = new Stopwatch(); int i; unchecked { #region 测试 // 硬编码.栈变量 long iOffset = 1; sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = p + iOffset; } sw.Stop(); sOut.AppendLine(string.Format("64硬编码.栈变量:\t{0}", sw.ElapsedMilliseconds)); // 硬编码.栈分配 long* pOffset = stackalloc long[1]; pOffset[0] = 1; sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = p + pOffset[0]; } sw.Stop(); sOut.AppendLine(string.Format("64硬编码.栈分配:\t{0}", sw.ElapsedMilliseconds)); // 硬编码.静态 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = p + TryIt_Static64_Offset; } sw.Stop(); sOut.AppendLine(string.Format("64硬编码.静态:\t{0}", sw.ElapsedMilliseconds)); // 静态调用 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = TryIt_Static64_Ptr(p); } sw.Stop(); sOut.AppendLine(string.Format("64静态调用:\t{0}", sw.ElapsedMilliseconds)); #endregion // 测试 } } /// <summary> /// 执行测试 - 非泛型 /// </summary> /// <param name="sOut">文本输出</param> private static unsafe void TryIt_NoGen(StringBuilder sOut, int CountLoop) { // 创建 PointerCallAdd pca = new PointerCallAdd(); SldPointerCallAdd dpca = new SldPointerCallAdd(); SPointerCallAdd spca; pca.Offset = 1; spca.Offset = 1; // 转型 PointerCall pca_base = pca; IPointerCall pca_itf = pca; IPointerCall dpca_itf = dpca; IPointerCall spca_itf = spca; // == 性能测试 == byte* p = null; Stopwatch sw = new Stopwatch(); int i; unchecked { #region 调用 #region 直接调用 // 调用派生类 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = pca.Ptr(p); } sw.Stop(); sOut.AppendLine(string.Format("调用派生类:\t{0}", sw.ElapsedMilliseconds)); // 调用密封类 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = dpca.Ptr(p); } sw.Stop(); sOut.AppendLine(string.Format("调用密封类:\t{0}", sw.ElapsedMilliseconds)); // 调用结构体 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = spca.Ptr(p); } sw.Stop(); sOut.AppendLine(string.Format("调用结构体:\t{0}", sw.ElapsedMilliseconds)); #endregion // 直接调用 #region 间接调用 // 调用基类 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = pca_base.Ptr(p); } sw.Stop(); sOut.AppendLine(string.Format("调用基类:\t{0}", sw.ElapsedMilliseconds)); // 调用派生类的接口 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = pca_itf.Ptr(p); } sw.Stop(); sOut.AppendLine(string.Format("调用派生类的接口:\t{0}", sw.ElapsedMilliseconds)); // 调用密封类的接口 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = dpca_itf.Ptr(p); } sw.Stop(); sOut.AppendLine(string.Format("调用密封类的接口:\t{0}", sw.ElapsedMilliseconds)); // 调用结构体的接口 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = spca_itf.Ptr(p); } sw.Stop(); sOut.AppendLine(string.Format("调用结构体的接口:\t{0}", sw.ElapsedMilliseconds)); #endregion // 间接调用 #endregion // 调用 #region 泛型调用 #region 泛型基类约束 // 基类泛型调用派生类 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallClassPtr(pca, p); } sw.Stop(); sOut.AppendLine(string.Format("基类泛型调用派生类:\t{0}", sw.ElapsedMilliseconds)); // 基类泛型调用基类 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallClassPtr(pca_base, p); } sw.Stop(); sOut.AppendLine(string.Format("基类泛型调用基类:\t{0}", sw.ElapsedMilliseconds)); #endregion // 泛型基类约束 #region 泛型接口约束 - 直接调用 // 接口泛型调用派生类 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallPtr(pca, p); } sw.Stop(); sOut.AppendLine(string.Format("接口泛型调用派生类:\t{0}", sw.ElapsedMilliseconds)); // 接口泛型调用密封类 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallPtr(dpca, p); } sw.Stop(); sOut.AppendLine(string.Format("接口泛型调用密封类:\t{0}", sw.ElapsedMilliseconds)); // 接口泛型调用结构体 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallPtr(spca, p); } sw.Stop(); sOut.AppendLine(string.Format("接口泛型调用结构体:\t{0}", sw.ElapsedMilliseconds)); // 接口泛型调用结构体引用 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallRefPtr(ref spca, p); } sw.Stop(); sOut.AppendLine(string.Format("接口泛型调用结构体引用:\t{0}", sw.ElapsedMilliseconds)); #endregion // 直接调用 #region 间接调用 // 接口泛型调用基类 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallPtr(pca_base, p); } sw.Stop(); sOut.AppendLine(string.Format("接口泛型调用基类:\t{0}", sw.ElapsedMilliseconds)); // 接口泛型调用派生类的接口 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallPtr(pca_itf, p); } sw.Stop(); sOut.AppendLine(string.Format("接口泛型调用派生类的接口:\t{0}", sw.ElapsedMilliseconds)); // 接口泛型调用密封类的接口 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallPtr(dpca_itf, p); } sw.Stop(); sOut.AppendLine(string.Format("接口泛型调用密封类的接口:\t{0}", sw.ElapsedMilliseconds)); // 接口泛型调用结构体的接口 sw.Reset(); sw.Start(); for (i = 0; i < CountLoop; ++i) { p = CallPtr(spca_itf, p); } sw.Stop(); sOut.AppendLine(string.Format("接口泛型调用结构体的接口:\t{0}", sw.ElapsedMilliseconds)); #endregion // 间接调用 #endregion // 泛型调用 } } /// <summary> /// 执行测试 - 泛型 /// </summary> /// <param name="sOut">文本输出</param> private static unsafe void TryIt_Gen(StringBuilder sOut, int CountLoop) { // !!! C#不支持将整数类型作为泛型约束 !!! } /// <summary> /// 执行测试 /// </summary> public static string TryIt() { StringBuilder sOut = new StringBuilder(); sOut.AppendLine("== PointerCallTool.TryIt() =="); TryIt_Static(sOut, CountLoop); TryIt_Static64(sOut, CountLoop); TryIt_NoGen(sOut, CountLoop); TryIt_Gen(sOut, CountLoop); sOut.AppendLine(); return sOut.ToString(); } /// <summary> /// 执行测试 - static /// </summary> public static string TryItStatic() { StringBuilder sOut = new StringBuilder(); int cnt = CountLoop * 10; sOut.AppendLine("== PointerCallTool.TryItStatic() =="); TryIt_Static(sOut, cnt); TryIt_Static64(sOut, cnt); sOut.AppendLine(); return sOut.ToString(); } } #endregion }
二、测试环境
编译器——
VS2005:Visual Studio 2005 SP1。
VS2010:Visual Studio 2010 SP1。
采用上述编译器编译为Release版程序,最大速度优化。
机器A——
HP CQ42-153TX
处理器:Intel Core i5-430M(2.26GHz, Turbo 2.53GHz, 3MB L3)
内存容量:2GB (DDR3-1066)
机器B——
DELL Latitude E6320
处理器:Intel i3-2310M(2.1GHz, 3MB L3)
内存容量:4GB (DDR3-1333,双通道)
测试环境——
A_2005:机器A,VS2005,Window 7 32位。
A_2010:机器A,VS2010,Window 7 32位。
B_2005:机器B,VS2005,Window XP SP3 32位。
B_2010:机器B,VS2010,Window XP SP3 32位。
B64_2005:机器B,VS2005,Window 7 64位(x64)。
B64_2010:机器B,VS2010,Window 7 64位(x64)。
三、硬编码与静态调用 的测试结果(栈变量、栈分配、64位整数)
因为硬编码与静态调用很可能会被执行函数展开优化,速度明显比其他测试项目要快。所我另外写了一个测试函数(TryItStatic),将循环次数设为原来的10倍。
测试结果如下(单位:毫秒)——
模式 | A_2005 | A_2010 | B_2005 | B_2010 | B64_2005 | B64_2010 |
---|---|---|---|---|---|---|
硬编码.栈变量: | 1608 | 1623 | 957 | 966 | 960 | 959 |
硬编码.栈分配: | 1612 | 1617 | 1073 | 957 | 961 | 960 |
硬编码.静态: | 1609 | 1613 | 957 | 971 | 961 | 960 |
静态调用: | 1608 | 1611 | 1063 | 958 | 961 | 963 |
64硬编码.栈变量: | 1610 | 1617 | 967 | 957 | 959 | 1010 |
64硬编码.栈分配: | 1610 | 1619 | 1034 | 957 | 960 | 1012 |
64硬编码.静态: | 1609 | 1618 | 999 | 996 | 957 | 1010 |
64静态调用: | 1610 | 1615 | 959 | 1002 | 957 | 7696 |
结果分析——
先看32位与64位的区别。发现在大多数情况,32位与64位的速度是一样的。唯一就是64位整数运算代码在“64位平台+VS2010”上运行时,速度比在32位下还慢,尤其是静态调用慢了好几倍,硬编码代码的速度也有所下降。真的很奇怪,既然运行的是同一份程序,为什么64位比32位还慢,难道是.Net 4.0在x64平台上的即时编译器的问题?不解。
栈变量、栈分配、静态变量的访问速度几乎一致,看来可以放心地随意使用。
看来以后写指针操作代码时,只写64位整数版就行了。
四、密封类 的测试结果
测试结果如下(单位:毫秒)——
模式 A_2005 A_2010 B_2005 B_2010 B64_2005B64_2010
硬编码.栈变量: 162 162 95 95 96 95
硬编码.栈分配: 161 161 95 95 95 97
硬编码.静态: 161 165 97 95 97 95
静态调用: 161 163 95 95 96 97
64硬编码.栈变量: 161161989596 100
64硬编码.栈分配: 160162959795 100
64硬编码.静态: 162 162 95 97 95 100
64静态调用: 161 161 95 95 97 770
调用派生类: 563 568 670 668 676 580
调用密封类: 161 162 101 103 102 767
调用结构体: 163 161 116 102 191 772
调用基类: 566 573 668 660 675 577
调用派生类的接口: 727 731 767 862 862 770
调用密封类的接口: 721 730 957 862 870 771
调用结构体的接口: 104511341318134013441253
基类泛型调用派生类: 910795127478912561287
基类泛型调用基类: 902 785 1092 676 1346 1250
接口泛型调用派生类: 1407733163486216331633
接口泛型调用密封类: 1405808173395617431638
接口泛型调用结构体: 5661606711018641250
接口泛型调用结构体引用: 48016170098769961
接口泛型调用基类: 1409728176776416311635
接口泛型调用派生类的接口: 1410727170296617301634
接口泛型调用密封类的接口: 1402808171995816351637
接口泛型调用结构体的接口: 161711281859149922082117
将测试结果重新排版一下,突出不同实现方法的速度区别——
环境 | 分类 | 基类 | 派生类 | 密封类 | 结构体 | 结构体的引用 |
---|---|---|---|---|---|---|
A_2005 | 直接调用 | 566 | 563 | 161 | 163 | |
接口调用 | 727 | 721 | 1045 | |||
基类约束泛型调用 | 902 | 910 | ||||
接口约束泛型调用 | 1407 | 1405 | 566 | 480 | ||
接口约束泛型调用接口 | 1409 | 1410 | 1402 | 1617 | ||
A_2010 | 直接调用 | 573 | 568 | 162 | 161 | |
接口调用 | 731 | 730 | 1134 | |||
基类约束泛型调用 | 785 | 795 | ||||
接口约束泛型调用 | 733 | 808 | 160 | 161 | ||
接口约束泛型调用接口 | 728 | 727 | 808 | 1128 | ||
B_2005 | 直接调用 | 668 | 670 | 101 | 116 | |
接口调用 | 767 | 957 | 1318 | |||
基类约束泛型调用 | 1092 | 1274 | ||||
接口约束泛型调用 | 1634 | 1733 | 671 | 700 | ||
接口约束泛型调用接口 | 1767 | 1702 | 1719 | 1859 | ||
B_2010 | 直接调用 | 660 | 668 | 103 | 102 | |
接口调用 | 862 | 862 | 1340 | |||
基类约束泛型调用 | 676 | 789 | ||||
接口约束泛型调用 | 862 | 956 | 101 | 98 | ||
接口约束泛型调用接口 | 764 | 966 | 958 | 1499 | ||
B64_2005 | 直接调用 | 675 | 676 | 102 | 191 | |
接口调用 | 862 | 870 | 1344 | |||
基类约束泛型调用 | 1346 | 1256 | ||||
接口约束泛型调用 | 1633 | 1743 | 864 | 769 | ||
接口约束泛型调用接口 | 1631 | 1730 | 1635 | 2208 | ||
B64_2010 | 直接调用 | 577 | 580 | 767 | 772 | |
接口调用 | 770 | 771 | 1253 | |||
基类约束泛型调用 | 1250 | 1287 | ||||
接口约束泛型调用 | 1633 | 1638 | 1250 | 961 | ||
接口约束泛型调用接口 | 1635 | 1634 | 1637 | 2117 |
综合来看,密封类的性能最好,在大多数测试项目中名列前茅——
“直接调用”时能被内联(inline)优化,与“硬编码”一样快,快于派生类。
“接口调用”、“泛型调用接口”时与派生类性能一致,快于结构体的“接口调用”。
唯一就是在“泛型调用”时,落后于结构体,与派生类差不多稍微慢一点。
再就是奇怪的“64位平台+VS2010”问题,密封类、结构体在直接调用时,还不如派生类。
最后总结一下可能会被内联优化的调用类型——
32位平台+VS2005:调用密封类、调用结构体。
32位平台+VS2010:调用密封类、调用结构体、接口约束泛型调用结构体。
64位平台+VS2005:调用密封类、调用结构体。
64位平台+VS2010:(无)。
(完)
测试程序exe——
http://115.com/file/e6ymd5fe
http://download.csdn.net/detail/zyl910/3619643
源代码下载——
http://115.com/file/aqz167n9
http://download.csdn.net/detail/zyl910/3619647
目录——
C#类与结构体究竟谁快——各种函数调用模式速度评测:http://www.cnblogs.com/zyl910/archive/2011/09/19/2186623.html
再探C#类与结构体究竟谁快——考虑栈变量、栈分配、64位整数、密封类:http://www.cnblogs.com/zyl910/archive/2011/09/20/2186622.html
三探C#类与结构体究竟谁快——MSIL(微软中间语言)解读:http://www.cnblogs.com/zyl910/archive/2011/09/24/2189403.html
四探C#类与结构体究竟谁快——跨程序集(assembly)调用:http://www.cnblogs.com/zyl910/archive/2011/10/01/2197844.html