博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C#类与结构体究竟谁快——各种函数调用模式速度评测
阅读量:5317 次
发布时间:2019-06-14

本文共 9335 字,大约阅读时间需要 31 分钟。

以前我一直有个疑惑——在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{	/// 	/// 指针操作接口	/// 	public interface IPointerCall	{		/// 		/// 指针操作		/// 		/// 源指针		/// 
修改后指针
unsafe byte* Ptr(byte* p); }#region 非泛型 /// /// [非泛型] 指针操作基类 /// public abstract class PointerCall : IPointerCall { public abstract unsafe byte* Ptr(byte* p); } /// /// [非泛型] 指针操作派生类: 指针+偏移 /// public class PointerCallAdd : PointerCall { /// /// 偏移值 /// public int Offset = 0; public override unsafe byte* Ptr(byte* p) { return unchecked(p + Offset); } } /// /// [非泛型] 指针操作结构体: 指针+偏移 /// public struct SPointerCallAdd : IPointerCall { /// /// 偏移值 /// public int Offset; public unsafe byte* Ptr(byte* p) { return unchecked(p + Offset); } }#endregion#region 泛型 // !!! C#不支持将整数类型作为泛型约束 !!! //public abstract class GenPointerCall
: IPointerCall where T: int, long //{ // public abstract unsafe byte* Ptr(byte* p); // void d() // { // } //}#endregion#region 全部测试 ///
/// 指针操作的一些常用函数 /// public static class PointerCallTool { private const int CountLoop = 200000000; // 循环次数 ///
/// 调用指针操作 /// ///
具有IPointerCall接口的类型。
///
调用者 ///
源指针 ///
修改后指针
public static unsafe byte* CallPtr
(T ptrcall, byte* p) where T : IPointerCall { return ptrcall.Ptr(p); } public static unsafe byte* CallClassPtr
(T ptrcall, byte* p) where T : PointerCall { return ptrcall.Ptr(p); } public static unsafe byte* CallRefPtr
(ref T ptrcall, byte* p) where T : IPointerCall { return ptrcall.Ptr(p); } // C#不允许将特定的结构体作为泛型约束。所以对于结构体只能采用上面那个方法,通过IPointerCall接口进行约束,可能会造成性能下降。 //public static unsafe byte* SCallPtr
(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); } ///
/// 执行测试 - 静态调用 /// ///
文本输出 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 // 测试 } } ///
/// 执行测试 - 非泛型 /// ///
文本输出 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 // 泛型调用 } } ///
/// 执行测试 - 泛型 /// ///
文本输出 private static unsafe void TryIt_Gen(StringBuilder sOut) { // !!! C#不支持将整数类型作为泛型约束 !!! } ///
/// 执行测试 /// 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——

源代码下载——

目录——
C#类与结构体究竟谁快——各种函数调用模式速度评测:
再探C#类与结构体究竟谁快——考虑栈变量、栈分配、64位整数、密封类:
三探C#类与结构体究竟谁快——MSIL(微软中间语言)解读:
四探C#类与结构体究竟谁快——跨程序集(assembly)调用:

转载于:https://www.cnblogs.com/zyl910/archive/2011/09/19/2186623.html

你可能感兴趣的文章