程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 再探C#類與結構體究竟誰快——考慮棧變量、棧分配、64位整數、密封類

再探C#類與結構體究竟誰快——考慮棧變量、棧分配、64位整數、密封類

編輯:C#入門知識

 

上次我對C#類與結構體做了一次速度評測:http://www.BkJia.com/kf/201110/106554.html 。經過一段時間思索,發現還可以進一步探討——

 

第一、棧變量。上次的“硬編碼”,是訪問類中的靜態變量的。若改為訪問函數中的棧變量,性能會不會有所提高?
第二、棧分配(stackalloc)。既然要測試棧變量,我們還可以順便測試一下在棧上分配的內存塊的訪問性能。
第三、64位整數。由於32位系統的成功,我們已經習慣了使用32位整數(int)。現在64位系統逐漸普及,我們得為此做好准備。對於指針操作時經常要用到的偏移量增減運算來說,是使用32位整數,還是使用64位整數,或寫兩套代碼?這需要測試後才能決定。
第四、密封類(sealed)。聽說密封類能提高性能,我們可以測試一下。有兩種測試方式,一是為原來的派生類增加sealed關鍵字,二是專門另外寫一個密封類。我決定同時使用這兩種方法,分別測試其性能。




一、測試代碼


  測試代碼如下

 

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 sealed class PointerCallAdd : PointerCall 

    { 

        /// <summary> 

        /// 偏移值 

        /// </summary> 

        public int Offset = 0; 

 

        public override unsafe byte* Ptr(byte* p) 

        { 

            return unchecked(p + Offset); 

        } 

    } 

 

    /// <summary> 

    /// [非泛型] 指針操作密封類: 指針+偏移 

    /// </summary> 

    public sealed class SldPointerCallAdd : IPointerCall 

    { 

        /// <summary> 

        /// 偏移值 

        /// </summary> 

        public int Offset = 0; 

 

        public 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 

    { 

#if DEBUG 

        private const int CountLoop = 10000000; // 循環次數 

#else 

        private const int CountLoop = 200000000;    // 循環次數 

#endif 

 

        /// <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, int CountLoop) 

        { 

            TryIt_Static_Offset = 1; 

 

            // == 性能測試== 

            byte* p = null; 

            Stopwatch sw = new Stopwatch(); 

            int i; 

            unchecked 

            { 

                #region 測試 

                // 硬編碼.棧變量 

                int iOffset = 1; 

                sw.Reset(); 

                sw.Start(); 

                for (i = 0; i < CountLoop; ++i) 

                { 

                    p = p + iOffset; 

                } 

                sw.Stop(); 

                sOut.AppendLine(string.Format("硬編碼.棧變量:\t{0}", sw.ElapsedMilliseconds)); 

 

                // 硬編碼.棧分配 

                int* pOffset = stackalloc int[1]; 

                pOffset[0] = 1; 

                sw.Reset(); 

                sw.Start(); 

                for (i = 0; i < CountLoop; ++i) 

                { 

                    p = p + pOffset[0]; 

                } 

                sw.Stop(); 

                sOut.AppendLine(string.Format("硬編碼.棧分配:\t{0}", sw.ElapsedMilliseconds)); 

 

                // 硬編碼.靜態 

                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 long TryIt_Static64_Offset; 

        private static unsafe byte* TryIt_Static64_Ptr(byte* p) 

        { 

            return unchecked(p + TryIt_Static64_Offset); 

        } 

        /// <summary> 

        /// 執行測試- 靜態調用 

        /// </summary> 

        /// <param name="sOut">文本輸出</param> 

        private static unsafe void TryIt_Static64(StringBuilder sOut, int CountLoop) 

        { 

            TryIt_Static64_Offset = 1; 

 

            // == 性能測試== 

            byte* p = null; 

            Stopwatch sw = new Stopwatch(); 

            int i; 

            unchecked 

            { 

                #region 測試 

                // 硬編碼.棧變量 

                long iOffset = 1; 

                sw.Reset(); 

                sw.Start(); 

                for (i = 0; i < CountLoop; ++i) 

                { 

                    p = p + iOffset; 

                } 

                sw.Stop(); 

                sOut.AppendLine(string.Format("64硬編碼.棧變量:\t{0}", sw.ElapsedMilliseconds)); 

 

                // 硬編碼.棧分配 

                long* pOffset = stackalloc long[1]; 

                pOffset[0] = 1; 

                sw.Reset(); 

                sw.Start(); 

                for (i = 0; i < CountLoop; ++i) 

                { 

                    p = p + pOffset[0]; 

                } 

                sw.Stop(); 

                sOut.AppendLine(string.Format("64硬編碼.棧分配:\t{0}", sw.ElapsedMilliseconds)); 

 

                // 硬編碼.靜態 

                sw.Reset(); 

                sw.Start(); 

                for (i = 0; i < CountLoop; ++i) 

                { 

                    p = p + TryIt_Static64_Offset; 

                } 

                sw.Stop(); 

                sOut.AppendLine(string.Format("64硬編碼.靜態:\t{0}", sw.ElapsedMilliseconds)); 

 

                // 靜態調用 

                sw.Reset(); 

                sw.Start(); 

                for (i = 0; i < CountLoop; ++i) 

                { 

                    p = TryIt_Static64_Ptr(p); 

                } 

                sw.Stop(); 

                sOut.AppendLine(string.Format("64靜態調用:\t{0}", sw.ElapsedMilliseconds)); 

 

                #endregion // 測試 

            } 

        } 

 

        /// <summary> 

        /// 執行測試- 非泛型 

        /// </summary> 

        /// <param name="sOut">文本輸出</param> 

        private static unsafe void TryIt_NoGen(StringBuilder sOut, int CountLoop) 

        { 

            // 創建 

            PointerCallAdd pca = new PointerCallAdd(); 

            SldPointerCallAdd dpca = new SldPointerCallAdd(); 

            SPointerCallAdd spca; 

            pca.Offset = 1; 

            spca.Offset = 1; 

 

            // 轉型 

            PointerCall pca_base = pca; 

            IPointerCall pca_itf = pca; 

            IPointerCall dpca_itf = dpca; 

            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 = dpca.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 = dpca_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(dpca, 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(dpca_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, int CountLoop) 

        { 

            // !!! C#不支持將整數類型作為泛型約束!!! 

        } 

 

        /// <summary> 

        /// 執行測試 

        /// </summary> 

        public static string TryIt() 

        { 

            StringBuilder sOut = new StringBuilder(); 

            sOut.AppendLine("== PointerCallTool.TryIt() =="); 

            TryIt_Static(sOut, CountLoop); 

            TryIt_Static64(sOut, CountLoop); 

            TryIt_NoGen(sOut, CountLoop); 

            TryIt_Gen(sOut, CountLoop); 

            sOut.AppendLine(); 

            return sOut.ToString(); 

        } 

 

        /// <summary> 

        /// 執行測試- static 

        /// </summary> 

        public static string TryItStatic() 

        { 

            StringBuilder sOut = new StringBuilder(); 

            int cnt = CountLoop * 10; 

            sOut.AppendLine("== PointerCallTool.TryItStatic() =="); 

            TryIt_Static(sOut, cnt); 

            TryIt_Static64(sOut, cnt); 

            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 XP SP3 32位。
B_2010:機器B,VS2010,Window XP SP3 32位。
B64_2005:機器B,VS2005,Window 7 64位(x64)。
B64_2010:機器B,VS2010,Window 7 64位(x64)。






三、硬編碼與靜態調用 的測試結果(棧變量、棧分配、64位整數)



  因為硬編碼與靜態調用很可能會被執行函數展開優化,速度明顯比其他測試項目要快。所我另外寫了一個測試函數(TryItStatic),將循環次數設為原來的10倍。


  測試結果如下(單位:毫秒)——


模式 A_2005 A_2010 B_2005 B_2010 B64_2005 B64_2010 硬編碼.棧變量: 1608 1623 957 966 960 959 硬編碼.棧分配: 1612 1617 1073 957 961 960 硬編碼.靜態: 1609 1613 957 971 961 960 靜態調用: 1608 1611 1063 958 961 963 64硬編碼.棧變量: 1610 1617 967 957 959 1010 64硬編碼.棧分配: 1610 1619 1034 957 960 1012 64硬編碼.靜態: 1609 1618 999 996 957 1010 64靜態調用: 1610 1615 959 1002 957 7696


  結果分析——
先看32位與64位的區別。發現在大多數情況,32位與64位的速度是一樣的。唯一就是64位整數運算代碼在“64位平台+VS2010”上運行時,速度比在32位下還慢,尤其是靜態調用慢了好幾倍,硬編碼代碼的速度也有所下降。真的很奇怪,既然運行的是同一份程序,為什麼64位比32位還慢,難道是.Net 4.0在x64平台上的即時編譯器的問題?不解。
棧變量、棧分配、靜態變量的訪問速度幾乎一致,看來可以放心地隨意使用。


  看來以後寫指針操作代碼時,只寫64位整數版就行了。






四、密封類 的測試結果



  測試結果如下(單位:毫秒)——
模式 A_2005  A_2010 B_2005  B_2010 B64_2005B64_2010
硬編碼.棧變量: 162  162 95  95 96 95
硬編碼.棧分配: 161  161 95  95 95 97
硬編碼.靜態: 161  165 97  95 97 95
靜態調用: 161  163 95  95 96 97
64硬編碼.棧變量: 161161989596100
64硬編碼.棧分配: 160162959795100
64硬編碼.靜態: 162  162 95  97 95 100
64靜態調用: 161  161 95  95 97 770
調用派生類: 563  568 670  668 676  580
調用密封類: 161  162 101  103 102  767
調用結構體: 163  161 116  102 191  772
調用基類: 566  573 668  660 675  577
調用派生類的接口: 727  731 767  862 862  770
調用密封類的接口: 721  730 957  862 870  771
調用結構體的接口: 104511341318134013441253
基類泛型調用派生類: 910795127478912561287
基類泛型調用基類: 902  785 1092  676 1346  1250
接口泛型調用派生類: 1407733163486216331633
接口泛型調用密封類: 1405808173395617431638
接口泛型調用結構體: 5661606711018641250
接口泛型調用結構體引用: 48016170098769961
接口泛型調用基類: 1409728176776416311635
接口泛型調用派生類的接口: 1410727170296617301634
接口泛型調用密封類的接口: 1402808171995816351637
接口泛型調用結構體的接口: 161711281859149922082117

  將測試結果重新排版一下,突出不同實現方法的速度區別——



環境 分類 基類 派生類 密封類 結構體 結構體的引用 A_2005 直接調用 566 563 161 163     接口調用   727 721 1045     基類約束泛型調用 902 910         接口約束泛型調用   1407 1405 566 480   接口約束泛型調用接口 1409 1410 1402 1617   A_2010 直接調用 573 568 162 161     接口調用   731 730 1134     基類約束泛型調用 785 795         接口約束泛型調用   733 808 160 161   接口約束泛型調用接口 728 727 808 1128   B_2005 直接調用 668 670 101 116     接口調用   767 957 1318     基類約束泛型調用 1092 1274         接口約束泛型調用   1634 1733 671 700   接口約束泛型調用接口 1767 1702 1719 1859   B_2010 直接調用 660 668 103 102     接口調用   862 862 1340     基類約束泛型調用 676 789         接口約束泛型調用   862 956 101 98   接口約束泛型調用接口 764 966 958 1499   B64_2005 直接調用 675 676 102 191     接口調用   862 870 1344     基類約束泛型調用 1346 1256         接口約束泛型調用   1633 1743 864 769   接口約束泛型調用接口 1631 1730 1635 2208   B64_2010 直接調用 577 580 767 772     接口調用   770 771 1253     基類約束泛型調用 1250 1287         接口約束泛型調用   1633 1638 1250 961   接口約束泛型調用接口 1635 1634 1637 2117  



  綜合來看,密封類的性能最好,在大多數測試項目中名列前茅——
“直接調用”時能被內聯(inline)優化,與“硬編碼”一樣快,快於派生類。
“接口調用”、“泛型調用接口”時與派生類性能一致,快於結構體的“接口調用”。
唯一就是在“泛型調用”時,落後於結構體,與派生類差不多稍微慢一點。
再就是奇怪的“64位平台+VS2010”問題,密封類、結構體在直接調用時,還不如派生類。


  最後總結一下可能會被內聯優化的調用類型——
32位平台+VS2005:調用密封類、調用結構體。
32位平台+VS2010:調用密封類、調用結構體、接口約束泛型調用結構體。
64位平台+VS2005:調用密封類、調用結構體。
64位平台+VS2010:(無)。
 

zyl910的專欄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved