以前我一直有個疑惑——在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