以前我一直有個疑惑——在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
{/// <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 class PointerCallAdd : PointerCall{/// <summary>/// 偏移值/// </summary>public int Offset = 0;public override 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{private const int CountLoop = 200000000; // 循環次數/// <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){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 // 測試}}/// <summary>/// 執行測試 - 非泛型/// </summary>/// <param name="sOut">文本輸出</param>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 // 泛型調用}}/// <summary>/// 執行測試 - 泛型/// </summary>/// <param name="sOut">文本輸出</param>private static unsafe void TryIt_Gen(StringBuilder sOut){// !!! C#不支持將整數類型作為泛型約束 !!!}/// <summary>/// 執行測試/// </summary>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}
編譯器——
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_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 |
結果分析——
先看第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.csdn.net/detail/zyl910/3614511
源代碼下載——
http://115.com/file/aqz70zy3
http://download.csdn.net/detail/zyl910/3614514
目錄——
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
轉載于:https://www.cnblogs.com/zyl910/archive/2011/09/19/2186623.html
總結
以上是生活随笔為你收集整理的C#类与结构体究竟谁快——各种函数调用模式速度评测的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。