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 (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_2005A_2010B_2005B_2010B_2010xp
硬编码163162232495
静态调用162161232395
调用派生类570487456487606
调用结构体16216095620100
调用基类565571453513874
调用派生类的接口810728779708929
调用结构体的接口10521055117511751267
基类泛型调用派生类97556810551148671
基类泛型调用基类98456910551152671
接口泛型调用派生类1383729134615311062
接口泛型调用结构体5661627671149107
接口泛型调用结构体引用487164752816100
接口泛型调用基类1378812133715351072
接口泛型调用派生类的接口1376810133815331102
接口泛型调用结构体的接口15421133248620131365


结果分析——
先看第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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/298060.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HelloSilverlight

一&#xff1a;输入姓名并选中一个日期&#xff0c;将在下面显示 二:XAML代码 <UserControl x:Class"HelloSilverlight.MainPage"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2…

Building a Space Station--POJ 2031

1、题目类型&#xff1a;计算几何&#xff0c;最小生成树。 2、解题思路&#xff1a;&#xff08;1&#xff09;获得所有点路径长度的矩阵map[][]&#xff1b;&#xff08;2&#xff09;利用Prim算法求解最小生成树。 3、注意事项&#xff1a;数学操作&#xff0c;中间值全部用…

21世纪7大数学难题,解决其中一个你就成为了百万富翁!

全世界只有3.14 % 的人关注了爆炸吧知识百万富翁你也可以昨天一大早&#xff0c;知识君就收到模友送的3枝红玫瑰。仔细一看&#xff0c;原来又是来跟知识君约稿的。。。知识君只能说&#xff1a;1900年&#xff0c;希尔伯特&#xff08;传送门&#xff09;在巴黎国际数学家代表…

在 Azure VM 上使用 Jitsi 搭建私人视频会议

点击上方蓝字 / 关注“汪宇杰博客”原文&#xff1a;Azure Tips And Tricks翻译&#xff1a;汪宇杰私人视频会议市面上有许多视频会议应用程序&#xff0c;例如 Zoom、Microsoft Teams 和 Skype。有时&#xff0c;您需要自己的服务&#xff0c;以让自己更安全并在自己的公司内部…

php 筛选数组,2020-07-24 php 通过数组键值对筛选数组

筛选数组 $listMenuArray([0] > Array([type] > 0[min] > 0)[1] > Array([type] > 1[min] > 1))目标数组 $resArray([0] > Array([id] > 183[type] > 0[min] > 0)[1] > Array([id] > 184[type] > 0[min] > 1)[2] > Array([id] &g…

python省市区三级联动_Django Admin实现三级联动的示例代码(省市区)

通过自定义Admin的模板文件实现省市区的三级联动.要求创建记录时,根据省>市>区的顺序选择依次显示对应数据.修改记录时默认显示已存在的数据.Modelclass Member(models.Model):name models.CharField(max_length100, verbose_name姓名)province models.CharField(max_l…

[LeetCode]119.Pascal#39;s Triangle II

题目 Given an index k, return the kth row of the Pascal’s triangle. For example, given k 3, Return [1,3,3,1]. Note: Could you optimize your algorithm to use only O(k) extra space? 思路 无 代码 /**------------------------------------* 日期&#xff1a…

2010.7.29 模式对话框

为什么点击ONOK后&#xff0c;对话框上的控件资源会被删除&#xff1f;OnOK做了什么事儿&#xff1f; 假如有一个对话框Class CMyDialog 我在CMyDialog中&#xff0c;声明了一个m_button&#xff0c;然后在OnInitDlg()中create这个buttton&#xff0c;即m_button.create() 然后…

终于有人做了我一直想做而不敢做的事。。

1 初中物理是不是学过&#xff0c;受力面积小&#xff0c;相应的压力就大&#xff5e;我觉得应该直接趴上去&#xff0c;一定行&#xff5e;反正我也是瞎说的2 不是我吹&#xff0c;换成是我&#xff0c;这包子能吃五屉3 交警蜀黍耐心的领着这位行人过马路&#xff0c;麻烦你快…

.NET 6 中的隐式命名空间引用

.NET 6 中的隐式命名空间引用Intro之前写过一篇隐式命名空间引用的大概介绍&#xff0c;在一些小的测试项目中也有在用&#xff0c;一直没作为示例给大家分享&#xff0c;主要原因在于之前看到了一个关于隐式命名空间引用的 Github issue 提到会有一些破坏性的变更&#xff0c;…

vscode函数跳转插件_人生苦短,我们为 Cocos Creator 开发的插件和工具

在使用 Cocos Creator 开发项目的过程中&#xff0c;为了提高开发效率我们开发了很多扩展插件&#xff0c;本文介绍常用的几款&#xff0c;抛砖引玉&#xff0c;希望给大家带来帮助。腾讯开心鼠英语网页扩展&#xff1a;运行时查看场景节点树Cocos Creator 本地项目通常会在 Ch…

SQLSERVER 日志收缩

SQL2008 的收缩日志 由于SQL2008对文件和日志管理进行了优化&#xff0c;所以以下语句在SQL2005中可以运行但在SQL2008中已经被取消&#xff1a;(SQL2005)BackupLog DNName with no_loggodumptransaction DNName with no_loggoUSE DNName DBCC SHRINKFILE (2)Go---------------…

压缩JS和CSS常用的工具

前些时候在发现将 JS和CSS压缩一下&#xff0c;的确有好处。在网上找了一下&#xff0c;发现下面的两款工具比较不错。 经过资源&#xff08;比如 CSS 和 JavaScrip 文件&#xff09;压缩测试了。 其中一个工具就是 YUI Compressor&#xff0c;一个来自Yahoo! Developer Networ…

php+转义实体字符,PHP针对HTML实体字符的转义函数

htmlspecialchars()转义特别的字符为HTML实体&#xff1b;& (ampersand) becomes & " (double quote) becomes " when ENT_NOQUOTES is not set. (single quote) becomes only when ENT_QUOTES is set. (greater than) becomes >htmlspeci…

解决win7“该文件没有与之关联的程序来执行该操作”

机器装好了win7系统。右击“计算机”管理的时候&#xff0c;出现“该文件没有与之关联的程序来执行该操作”能是因为删除了start menu下的某个文件,经过分析,找到了如下的解决方法:定位到注册表HKEY_CLASSES_ROOT\CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\shell\Manage\c…

绝不能放进微波炉的10样东西,最后一个太意外

全世界只有3.14 % 的人关注了爆炸吧知识前一阵&#xff0c;网上有一段用微波炉加热葡萄的视频&#xff0c;成了新闻热点。研究员在实验中发现&#xff0c;两颗葡萄放进微波炉后&#xff0c;竟然会产生电弧。图片来源网络之前小编加热汉堡的时候&#xff0c;本来想大快朵颐一顿&…

在 .NET 6 中使用 DATEONLY 和 TIMEONLY

在 .NET 6 中使用 DATEONLY 和 TIMEONLY在 即将发布的.NET 6中&#xff0c;引入了两种期待已久的类型作为核心库的一部分。DateOnly和TimeOnly允许开发人员表示 DateTime 的日期或时间部分。这两种新类型是结构体&#xff08;值类型&#xff09;&#xff0c;可以在您的代码独立…

vue 3.0 正式版_Vuejs 3 Release:One Piece. Vuejs 3.0 正式版发布!代号:海贼王

Vuejs 3.0 在北京时间2020年9月19日凌晨&#xff0c;终于发布了 3.0 版本&#xff0c;代号&#xff1a;One Piece。此次vue3.0 为用户提供了全新的 composition-api 以及更小的包大小&#xff0c;和更好的 TypeScript 支持。Vue3.0发布链接​github.comVue 是当前非常流行的框架…

i-doIT 0.9.9-7发布 CMDB配置管理数据库

i-doIT是一个基于ITIL技术的CMDB&#xff08;配置管理数据库&#xff09;。它能够记载IT系统及其变化&#xff0c;对变化定义了应急方案&#xff0c;以及显示重要信息&#xff0c;并有助于确保一个稳定和高效的IT网络运作。由于其模块化的架构&#xff0c;它可以部署功能性的附…

Linux IPC实践(6) --System V消息队列(3)

消息队列综合案例 消息队列实现回射客户/服务器 server进程接收时, 指定msgtyp为0, 从队首不断接收消息 server进程发送时, 将mtype指定为接收到的client进程的pid client进程发送的时候, mtype指定为自己进程的pid client进程接收时, 需要将msgtyp指定为自己进程的pid, 只接收…