C#类与结构体终归何人快——种种函数调用形式速度评测

以前作者一贯有个疑心——在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 (DDCRUISER3-1066)

机器B——
DELL Latitude E6320
处理器:Intel i3-2310M(2.1GHz, 3MB L3)
内存容积:4GB (DDTiggo3-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

相关文章