C#類與結構體究竟誰快——各種函數調用模式速度評測

來源:互聯網
上載者:User

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.