C#类与结构体究竟谁快——各种函数调用模式速度评测

本文探讨了在C#中使用类、结构体、接口和泛型进行指针操作封装的技术,并通过性能测试对比了不同封装方式在32位与64位平台上的表现,最终建议在追求性能的情况下优先考虑结构体封装。

以前我一直有个疑惑——在C#中,究竟是类(class)比较快,还是结构体(struct)比较快?
当时没有深究。

最近我遇到一个难题,需要将一些运算大的指针操作代码给封装一下。原先为了性能,这些代码是以硬编码的形式混杂在算法逻辑之中,不但影响了算法逻辑的可读性,其本身的指针操作代码枯燥、难懂、易写错,不易维护。所以我希望将其封装一下,简化代码编写、提高可维护性,但同时要尽可能地保证性能。
由于那些指针操作代码很灵活,简单的封装不能解决问题,还需要用到接口(interface)以实现一些动态调用功能。
为了简化代码,还打算实现一些泛型方法。
本来还想因32位指针、64位指针的不同而构造泛型类,可惜发现C#不支持将int/long作为泛型类型约束,只好作罢。将设计改为——分别为32位指针、64位指针编写不同的类,它们实现同一个接口。

在C#中,有两类封装技术——
1.基于类(class)的封装。在基类中定义好操作方法,然后在派生类中实现操作方法。
2.基于结构体(struct)的封装。在接口中定义好操作方法,然后在结构体中实现该接口的操作方法。
我分别使用这两类封装技术编写测试代码,然后做性能测试。

经过反复思索,考虑 类、结构体、接口、泛型 的组合,我找出了15种函数调用模式——
硬编码
静态调用
调用派生类
调用结构体
调用基类
调用派生类的接口
调用结构体的接口
基类泛型调用派生类
基类泛型调用基类
接口泛型调用派生类
接口泛型调用结构体
接口泛型调用结构体引用
接口泛型调用基类
接口泛型调用派生类的接口
接口泛型调用结构体的接口


测试代码为——

  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Text; 
  4. using System.Diagnostics; 
  5.  
  6. namespace TryPointerCall 
  7.     /// <summary> 
  8.     /// 指针操作接口 
  9.     /// </summary> 
  10.     public interface IPointerCall 
  11.     { 
  12.         /// <summary> 
  13.         /// 指针操作 
  14.         /// </summary> 
  15.         /// <param name="p">源指针</param> 
  16.         /// <returns>修改后指针</returns> 
  17.         unsafe byte* Ptr(byte* p); 
  18.     } 
  19. #region 非泛型 
  20.     /// <summary> 
  21.     /// [非泛型] 指针操作基类 
  22.     /// </summary> 
  23.     public abstract class PointerCall : IPointerCall 
  24.     { 
  25.         public abstract unsafe byte* Ptr(byte* p); 
  26.     } 
  27.  
  28.     /// <summary> 
  29.     /// [非泛型] 指针操作派生类: 指针+偏移 
  30.     /// </summary> 
  31.     public class PointerCallAdd : PointerCall 
  32.     { 
  33.         /// <summary> 
  34.         /// 偏移值 
  35.         /// </summary> 
  36.         public int Offset = 0; 
  37.  
  38.         public override unsafe byte* Ptr(byte* p) 
  39.         { 
  40.             return unchecked(p + Offset); 
  41.         } 
  42.     } 
  43.  
  44.     /// <summary> 
  45.     /// [非泛型] 指针操作结构体: 指针+偏移 
  46.     /// </summary> 
  47.     public struct SPointerCallAdd : IPointerCall 
  48.     { 
  49.         /// <summary> 
  50.         /// 偏移值 
  51.         /// </summary> 
  52.         public int Offset; 
  53.  
  54.         public unsafe byte* Ptr(byte* p) 
  55.         { 
  56.             return unchecked(p + Offset); 
  57.         } 
  58.     } 
  59. #endregion 
  60. #region 泛型 
  61.     // !!! C#不支持将整数类型作为泛型约束 !!! 
  62.     //public abstract class GenPointerCall<T> : IPointerCall where T: int, long 
  63.     //{ 
  64.     //    public abstract unsafe byte* Ptr(byte* p); 
  65.  
  66.     //    void d() 
  67.     //    { 
  68.     //    } 
  69.     //} 
  70. #endregion 
  71. #region 全部测试 
  72.     /// <summary> 
  73.     /// 指针操作的一些常用函数 
  74.     /// </summary> 
  75.     public static class PointerCallTool 
  76.     { 
  77.         private const int CountLoop = 200000000;    // 循环次数 
  78.  
  79.         /// <summary> 
  80.         /// 调用指针操作 
  81.         /// </summary> 
  82.         /// <typeparam name="T">具有IPointerCall接口的类型。</typeparam> 
  83.         /// <param name="ptrcall">调用者</param> 
  84.         /// <param name="p">源指针</param> 
  85.         /// <returns>修改后指针</returns> 
  86.         public static unsafe byte* CallPtr<T>(T ptrcall, byte* p) where T : IPointerCall 
  87.         { 
  88.             return ptrcall.Ptr(p); 
  89.         } 
  90.         public static unsafe byte* CallClassPtr<T>(T ptrcall, byte* p) where T : PointerCall 
  91.         { 
  92.             return ptrcall.Ptr(p); 
  93.         } 
  94.         public static unsafe byte* CallRefPtr<T>(ref T ptrcall, byte* p) where T : IPointerCall 
  95.         { 
  96.             return ptrcall.Ptr(p); 
  97.         } 
  98.  
  99.         // C#不允许将特定的结构体作为泛型约束。所以对于结构体只能采用上面那个方法,通过IPointerCall接口进行约束,可能会造成性能下降。 
  100.         //public static unsafe byte* SCallPtr<T>(T ptrcall, byte* p) where T : SPointerCallAdd 
  101.         //{ 
  102.         //    return ptrcall.Ptr(p); 
  103.         //} 
  104.  
  105.         private static int TryIt_Static_Offset; 
  106.         private static unsafe byte* TryIt_Static_Ptr(byte* p) 
  107.         { 
  108.             return unchecked(p + TryIt_Static_Offset); 
  109.         } 
  110.         /// <summary> 
  111.         /// 执行测试 - 静态调用 
  112.         /// </summary> 
  113.         /// <param name="sOut">文本输出</param> 
  114.         private static unsafe void TryIt_Static(StringBuilder sOut) 
  115.         { 
  116.             TryIt_Static_Offset = 1; 
  117.  
  118.             // == 性能测试 == 
  119.             byte* p = null
  120.             Stopwatch sw = new Stopwatch(); 
  121.             int i; 
  122.             unchecked 
  123.             { 
  124.                 #region 测试 
  125.                 // 硬编码 
  126.                 sw.Reset(); 
  127.                 sw.Start(); 
  128.                 for (i = 0; i < CountLoop; ++i) 
  129.                 { 
  130.                     p = p + TryIt_Static_Offset; 
  131.                 } 
  132.                 sw.Stop(); 
  133.                 sOut.AppendLine(string.Format("硬编码:\t{0}", sw.ElapsedMilliseconds)); 
  134.  
  135.                 // 静态调用 
  136.                 sw.Reset(); 
  137.                 sw.Start(); 
  138.                 for (i = 0; i < CountLoop; ++i) 
  139.                 { 
  140.                     p = TryIt_Static_Ptr(p); 
  141.                 } 
  142.                 sw.Stop(); 
  143.                 sOut.AppendLine(string.Format("静态调用:\t{0}", sw.ElapsedMilliseconds)); 
  144.                 #endregion // 测试 
  145.             } 
  146.         } 
  147.  
  148.         /// <summary> 
  149.         /// 执行测试 - 非泛型 
  150.         /// </summary> 
  151.         /// <param name="sOut">文本输出</param> 
  152.         private static unsafe void TryIt_NoGen(StringBuilder sOut) 
  153.         { 
  154.             // 创建 
  155.             PointerCallAdd pca = new PointerCallAdd(); 
  156.             SPointerCallAdd spca; 
  157.             pca.Offset = 1; 
  158.             spca.Offset = 1; 
  159.  
  160.             // 转型 
  161.             PointerCall pca_base = pca; 
  162.             IPointerCall pca_itf = pca; 
  163.             IPointerCall spca_itf = spca; 
  164.  
  165.             // == 性能测试 == 
  166.             byte* p = null
  167.             Stopwatch sw = new Stopwatch(); 
  168.             int i; 
  169.             unchecked 
  170.             { 
  171.                 #region 调用 
  172.                 #region 直接调用 
  173.                 // 调用派生类 
  174.                 sw.Reset(); 
  175.                 sw.Start(); 
  176.                 for (i = 0; i < CountLoop; ++i) 
  177.                 { 
  178.                     p = pca.Ptr(p); 
  179.                 } 
  180.                 sw.Stop(); 
  181.                 sOut.AppendLine(string.Format("调用派生类:\t{0}", sw.ElapsedMilliseconds)); 
  182.  
  183.                 // 调用结构体 
  184.                 sw.Reset(); 
  185.                 sw.Start(); 
  186.                 for (i = 0; i < CountLoop; ++i) 
  187.                 { 
  188.                     p = spca.Ptr(p); 
  189.                 } 
  190.                 sw.Stop(); 
  191.                 sOut.AppendLine(string.Format("调用结构体:\t{0}", sw.ElapsedMilliseconds)); 
  192.                 #endregion  // 直接调用 
  193.                 #region 间接调用 
  194.                 // 调用基类 
  195.                 sw.Reset(); 
  196.                 sw.Start(); 
  197.                 for (i = 0; i < CountLoop; ++i) 
  198.                 { 
  199.                     p = pca_base.Ptr(p); 
  200.                 } 
  201.                 sw.Stop(); 
  202.                 sOut.AppendLine(string.Format("调用基类:\t{0}", sw.ElapsedMilliseconds)); 
  203.  
  204.                 // 调用派生类的接口 
  205.                 sw.Reset(); 
  206.                 sw.Start(); 
  207.                 for (i = 0; i < CountLoop; ++i) 
  208.                 { 
  209.                     p = pca_itf.Ptr(p); 
  210.                 } 
  211.                 sw.Stop(); 
  212.                 sOut.AppendLine(string.Format("调用派生类的接口:\t{0}", sw.ElapsedMilliseconds)); 
  213.  
  214.                 // 调用结构体的接口 
  215.                 sw.Reset(); 
  216.                 sw.Start(); 
  217.                 for (i = 0; i < CountLoop; ++i) 
  218.                 { 
  219.                     p = spca_itf.Ptr(p); 
  220.                 } 
  221.                 sw.Stop(); 
  222.                 sOut.AppendLine(string.Format("调用结构体的接口:\t{0}", sw.ElapsedMilliseconds)); 
  223.                 #endregion  // 间接调用 
  224.                 #endregion  // 调用 
  225.                 #region 泛型调用 
  226.                 #region 泛型基类约束 
  227.                 // 基类泛型调用派生类 
  228.                 sw.Reset(); 
  229.                 sw.Start(); 
  230.                 for (i = 0; i < CountLoop; ++i) 
  231.                 { 
  232.                     p = CallClassPtr(pca, p); 
  233.                 } 
  234.                 sw.Stop(); 
  235.                 sOut.AppendLine(string.Format("基类泛型调用派生类:\t{0}", sw.ElapsedMilliseconds)); 
  236.  
  237.                 // 基类泛型调用基类 
  238.                 sw.Reset(); 
  239.                 sw.Start(); 
  240.                 for (i = 0; i < CountLoop; ++i) 
  241.                 { 
  242.                     p = CallClassPtr(pca_base, p); 
  243.                 } 
  244.                 sw.Stop(); 
  245.                 sOut.AppendLine(string.Format("基类泛型调用基类:\t{0}", sw.ElapsedMilliseconds)); 
  246.                 #endregion // 泛型基类约束 
  247.                 #region 泛型接口约束 - 直接调用 
  248.                 // 接口泛型调用派生类 
  249.                 sw.Reset(); 
  250.                 sw.Start(); 
  251.                 for (i = 0; i < CountLoop; ++i) 
  252.                 { 
  253.                     p = CallPtr(pca, p); 
  254.                 } 
  255.                 sw.Stop(); 
  256.                 sOut.AppendLine(string.Format("接口泛型调用派生类:\t{0}", sw.ElapsedMilliseconds)); 
  257.  
  258.                 // 接口泛型调用结构体 
  259.                 sw.Reset(); 
  260.                 sw.Start(); 
  261.                 for (i = 0; i < CountLoop; ++i) 
  262.                 { 
  263.                     p = CallPtr(spca, p); 
  264.                 } 
  265.                 sw.Stop(); 
  266.                 sOut.AppendLine(string.Format("接口泛型调用结构体:\t{0}", sw.ElapsedMilliseconds)); 
  267.  
  268.                 // 接口泛型调用结构体引用 
  269.                 sw.Reset(); 
  270.                 sw.Start(); 
  271.                 for (i = 0; i < CountLoop; ++i) 
  272.                 { 
  273.                     p = CallRefPtr(ref spca, p); 
  274.                 } 
  275.                 sw.Stop(); 
  276.                 sOut.AppendLine(string.Format("接口泛型调用结构体引用:\t{0}", sw.ElapsedMilliseconds)); 
  277.                 #endregion  // 直接调用 
  278.                 #region 间接调用 
  279.                 // 接口泛型调用基类 
  280.                 sw.Reset(); 
  281.                 sw.Start(); 
  282.                 for (i = 0; i < CountLoop; ++i) 
  283.                 { 
  284.                     p = CallPtr(pca_base, p); 
  285.                 } 
  286.                 sw.Stop(); 
  287.                 sOut.AppendLine(string.Format("接口泛型调用基类:\t{0}", sw.ElapsedMilliseconds)); 
  288.  
  289.                 // 接口泛型调用派生类的接口 
  290.                 sw.Reset(); 
  291.                 sw.Start(); 
  292.                 for (i = 0; i < CountLoop; ++i) 
  293.                 { 
  294.                     p = CallPtr(pca_itf, p); 
  295.                 } 
  296.                 sw.Stop(); 
  297.                 sOut.AppendLine(string.Format("接口泛型调用派生类的接口:\t{0}", sw.ElapsedMilliseconds)); 
  298.  
  299.                 // 接口泛型调用结构体的接口 
  300.                 sw.Reset(); 
  301.                 sw.Start(); 
  302.                 for (i = 0; i < CountLoop; ++i) 
  303.                 { 
  304.                     p = CallPtr(spca_itf, p); 
  305.                 } 
  306.                 sw.Stop(); 
  307.                 sOut.AppendLine(string.Format("接口泛型调用结构体的接口:\t{0}", sw.ElapsedMilliseconds)); 
  308.                 #endregion  // 间接调用 
  309.                 #endregion  // 泛型调用 
  310.  
  311.             } 
  312.         } 
  313.  
  314.         /// <summary> 
  315.         /// 执行测试 - 泛型 
  316.         /// </summary> 
  317.         /// <param name="sOut">文本输出</param> 
  318.         private static unsafe void TryIt_Gen(StringBuilder sOut) 
  319.         { 
  320.             // !!! C#不支持将整数类型作为泛型约束 !!! 
  321.         } 
  322.  
  323.         /// <summary> 
  324.         /// 执行测试 
  325.         /// </summary> 
  326.         public static string TryIt() 
  327.         { 
  328.             StringBuilder sOut = new StringBuilder(); 
  329.             sOut.AppendLine("== PointerCallTool.TryIt() =="); 
  330.             TryIt_Static(sOut); 
  331.             TryIt_NoGen(sOut); 
  332.             TryIt_Gen(sOut); 
  333.             sOut.AppendLine(); 
  334.             return sOut.ToString(); 
  335.         } 
  336.     } 
  337. #endregion 
  338.  



编译器——
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 7 64位(x64)。
B_2010:机器B,VS2010,Window 7 64位(x64)。
B_2010xp:机器B,VS2010,Window XP SP3 32位。

测试结果(单位:毫秒)——

模式A_2005A_2010B_2005B_2010B_2010xp
硬编码163162232495
静态调用162161232395
调用派生类570487456487606
调用结构体16216095620100
调用基类565571453513874
调用派生类的接口810728779708929
调用结构体的接口10521055117511751267
基类泛型调用派生类97556810551148671
基类泛型调用基类98456910551152671
接口泛型调用派生类1383729134615311062
接口泛型调用结构体5661627671149107
接口泛型调用结构体引用487164752816100
接口泛型调用基类1378812133715351072
接口泛型调用派生类的接口1376810133815331102
接口泛型调用结构体的接口15421133248620131365


结果分析——
先看第1列数据(A_2005),发现“静态调用”、“调用结构体”与“硬编码”的时间几乎一致,很可能做了函数展开优化。其次最快的是“接口泛型调用结构体引用”,比“接口泛型调用结构体”快了16%。但是“接口泛型调用结构体的接口”最慢,“调用结构体的接口”也比较慢。其他的基于类的调用模式的速度排在中间。而且发现泛型方法速度较慢。
然后看第2列数据(A_2010),发现“接口泛型调用结构体”、“接口泛型调用结构体引用”也与“硬编码”的时间几乎一致,表示它们也是做了函数展开优化的,看来在VS2010中不需要使用ref优化结构体参数。“调用结构体的接口”、“接口泛型调用结构体的接口”两个都成了垫底。泛型方法的速度有了很大的提高,几乎与非泛型调用速度相当。
再看第3列数据(B_2005),并与第1列(A_2005)进行比较,发现“静态调用”与“硬编码”的时间几乎一致,而“调用结构体”要慢一些。“接口泛型调用结构体”、“接口泛型调用结构体引用”比较慢,排在了“调用基类”、“调用派生类”的后面。可能是64位环境(x64)的特点吧。
再看第4列数据(B_2010),并与第3列(B_2005)进行比较,发现大部分变慢了,尤其是结构体相关的,难道VS2010的x64性能还不如VS2005?我将平台改为“x64”又编译了一次,结果依旧。
再看第5列数据(B_2010xp),发现32位WinXP下的大部分项目比64位Win7下要快,真诡异。而且发现“静态调用”、“调用结构体”与“硬编码”的时间几乎一致,看来“调用结构体”一直是被函数展开优化的,而64位下的静态调用有着更深层次的优化,所以比不过。


我觉得在要求性能的情况下,使用结构体封装指针操作比较好,因为直接调用时会做函数展开优化,大多数情况下与硬编码的性能一致。在遇到需要一些灵活功能时,可考虑采用“接口泛型调用结构体引用”的方式,速度有所下降。接口方式最慢,尽可能不用。一定要用接口的话,应优先选择非泛型版。

(完)

测试程序exe——
http://115.com/file/dn6hvcm3

http://download.youkuaiyun.com/detail/zyl910/3614511


源代码下载——
http://115.com/file/aqz70zy3

http://download.youkuaiyun.com/detail/zyl910/3614514

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值