以前我一直有个疑惑——在C#中,究竟是类(class)比较快,还是结构体(struct)比较快?
当时没有深究。最近我遇到一个难题,需要将一些运算大的指针操作代码给封装一下。原先为了性能,这些代码是以硬编码的形式混杂在算法逻辑之中,不但影响了算法逻辑的可读性,其本身的指针操作代码枯燥、难懂、易写错,不易维护。所以我希望将其封装一下,简化代码编写、提高可维护性,但同时要尽可能地保证性能。
由于那些指针操作代码很灵活,简单的封装不能解决问题,还需要用到接口(interface)以实现一些动态调用功能。为了简化代码,还打算实现一些泛型方法。本来还想因32位指针、64位指针的不同而构造泛型类,可惜发现C#不支持将int/long作为泛型类型约束,只好作罢。将设计改为——分别为32位指针、64位指针编写不同的类,它们实现同一个接口。在C#中,有两类封装技术——
1.基于类(class)的封装。在基类中定义好操作方法,然后在派生类中实现操作方法。2.基于结构体(struct)的封装。在接口中定义好操作方法,然后在结构体中实现该接口的操作方法。我分别使用这两类封装技术编写测试代码,然后做性能测试。经过反复思索,考虑 类、结构体、接口、泛型 的组合,我找出了15种函数调用模式——
硬编码静态调用调用派生类调用结构体调用基类调用派生类的接口调用结构体的接口基类泛型调用派生类基类泛型调用基类接口泛型调用派生类接口泛型调用结构体接口泛型调用结构体引用接口泛型调用基类接口泛型调用派生类的接口接口泛型调用结构体的接口 测试代码为——using System;using System.Collections.Generic;using System.Text;using System.Diagnostics;namespace TryPointerCall{ ////// 指针操作接口 /// public interface IPointerCall { ////// 指针操作 /// /// 源指针 ///修改后指针 unsafe byte* Ptr(byte* p); }#region 非泛型 ////// [非泛型] 指针操作基类 /// public abstract class PointerCall : IPointerCall { public abstract unsafe byte* Ptr(byte* p); } ////// [非泛型] 指针操作派生类: 指针+偏移 /// public class PointerCallAdd : PointerCall { ////// 偏移值 /// public int Offset = 0; public override unsafe byte* Ptr(byte* p) { return unchecked(p + Offset); } } ////// [非泛型] 指针操作结构体: 指针+偏移 /// public struct SPointerCallAdd : IPointerCall { ////// 偏移值 /// public int Offset; public unsafe byte* Ptr(byte* p) { return unchecked(p + Offset); } }#endregion#region 泛型 // !!! C#不支持将整数类型作为泛型约束 !!! //public abstract class GenPointerCall: IPointerCall where T: int, long //{ // public abstract unsafe byte* Ptr(byte* p); // void d() // { // } //}#endregion#region 全部测试 /// /// 指针操作的一些常用函数 /// public static class PointerCallTool { private const int CountLoop = 200000000; // 循环次数 ////// 调用指针操作 /// ///具有IPointerCall接口的类型。 /// 调用者 /// 源指针 ///修改后指针 public static unsafe byte* CallPtr(T ptrcall, byte* p) where T : IPointerCall { return ptrcall.Ptr(p); } public static unsafe byte* CallClassPtr (T ptrcall, byte* p) where T : PointerCall { return ptrcall.Ptr(p); } public static unsafe byte* CallRefPtr (ref T ptrcall, byte* p) where T : IPointerCall { return ptrcall.Ptr(p); } // C#不允许将特定的结构体作为泛型约束。所以对于结构体只能采用上面那个方法,通过IPointerCall接口进行约束,可能会造成性能下降。 //public static unsafe byte* SCallPtr (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); } /// /// 执行测试 - 静态调用 /// /// 文本输出 private static unsafe void TryIt_Static(StringBuilder sOut) { TryIt_Static_Offset = 1; // == 性能测试 == byte* p = null; Stopwatch sw = new Stopwatch(); int i; unchecked { #region 测试 // 硬编码 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 unsafe void TryIt_NoGen(StringBuilder sOut) { // 创建 PointerCallAdd pca = new PointerCallAdd(); SPointerCallAdd spca; pca.Offset = 1; spca.Offset = 1; // 转型 PointerCall pca_base = pca; IPointerCall pca_itf = pca; 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 = 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 = 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(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(spca_itf, p); } sw.Stop(); sOut.AppendLine(string.Format("接口泛型调用结构体的接口:\t{0}", sw.ElapsedMilliseconds)); #endregion // 间接调用 #endregion // 泛型调用 } } ////// 执行测试 - 泛型 /// /// 文本输出 private static unsafe void TryIt_Gen(StringBuilder sOut) { // !!! C#不支持将整数类型作为泛型约束 !!! } ////// 执行测试 /// public static string TryIt() { StringBuilder sOut = new StringBuilder(); sOut.AppendLine("== PointerCallTool.TryIt() =="); TryIt_Static(sOut); TryIt_NoGen(sOut); TryIt_Gen(sOut); sOut.AppendLine(); return sOut.ToString(); } }#endregion}
机器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 7 64位(x64)。B_2010:机器B,VS2010,Window 7 64位(x64)。B_2010xp:机器B,VS2010,Window XP SP3 32位。测试结果(单位:毫秒)——
模式 | A_2005 | A_2010 | B_2005 | B_2010 | B_2010xp |
硬编码 | 163 | 162 | 23 | 24 | 95 |
静态调用 | 162 | 161 | 23 | 23 | 95 |
调用派生类 | 570 | 487 | 456 | 487 | 606 |
调用结构体 | 162 | 160 | 95 | 620 | 100 |
调用基类 | 565 | 571 | 453 | 513 | 874 |
调用派生类的接口 | 810 | 728 | 779 | 708 | 929 |
调用结构体的接口 | 1052 | 1055 | 1175 | 1175 | 1267 |
基类泛型调用派生类 | 975 | 568 | 1055 | 1148 | 671 |
基类泛型调用基类 | 984 | 569 | 1055 | 1152 | 671 |
接口泛型调用派生类 | 1383 | 729 | 1346 | 1531 | 1062 |
接口泛型调用结构体 | 566 | 162 | 767 | 1149 | 107 |
接口泛型调用结构体引用 | 487 | 164 | 752 | 816 | 100 |
接口泛型调用基类 | 1378 | 812 | 1337 | 1535 | 1072 |
接口泛型调用派生类的接口 | 1376 | 810 | 1338 | 1533 | 1102 |
接口泛型调用结构体的接口 | 1542 | 1133 | 2486 | 2013 | 1365 |
(完)
测试程序exe——
源代码下载——
目录——C#类与结构体究竟谁快——各种函数调用模式速度评测:再探C#类与结构体究竟谁快——考虑栈变量、栈分配、64位整数、密封类:三探C#类与结构体究竟谁快——MSIL(微软中间语言)解读:四探C#类与结构体究竟谁快——跨程序集(assembly)调用: