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://blog.csdn.net/zyl910/article/details/6788417
再探C#类与结构体究竟谁快——考虑栈变量、栈分配、64位整数、密封类:http://blog.csdn.net/zyl910/article/details/6793908
三探C#类与结构体究竟谁快——MSIL(微软中间语言)解读:http://blog.csdn.net/zyl910/article/details/6817158
四探C#类与结构体究竟谁快——跨程序集(assembly)调用:http://blog.csdn.net/zyl910/article/details/6839868
目录——
C#类与结构体究竟谁快——各种函数调用模式速度评测:http://blog.csdn.net/zyl910/article/details/6788417
再探C#类与结构体究竟谁快——考虑栈变量、栈分配、64位整数、密封类:http://blog.csdn.net/zyl910/article/details/6793908
三探C#类与结构体究竟谁快——MSIL(微软中间语言)解读:http://blog.csdn.net/zyl910/article/details/6817158
四探C#类与结构体究竟谁快——跨程序集(assembly)调用:http://blog.csdn.net/zyl910/article/details/6839868

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
C是一种编程语言,由美国计算机科学家德尼斯·里奇在20世纪70年代初开发。它是一种通用的、结构化的高级编程语言。 C的设计目标是提供高效的编译器,并能与底层的操作系统进行良好的交互。因此,C语言广泛应用于系统编程、嵌入式系统、操作系统和编译器的开发以及其他对性能要求较高的领域。 C语言的语法简洁且灵活,支持面向过程的编程范式。C的核心功能包括变量、数据型、运算符、流程控制语句和函数。它也提供了许多标准库函数,使得开发者能够更方便地进行各种操作。 C语言具有良好的可移植性,且易于学习和使用。许多编程语言都受到C的影响,例如C++、C#、Java等。这些语言都从C中借鉴了语法和特性,因此学习C语言可以为后续学习其他编程语言打下坚实的基础。 尽管C语言在一些方面有限制和不足,但它仍然是许多开发人员和计算机科学家的首选语言之一。通过使用C语言,开发者可以更好地控制程序的细节,从而提高程序的性能和效率。此外,C语言的广泛应用也使得很多开源库和工具都以C语言编写,进一步扩展了C语言的应用领域。 总结来说,C语言是一种高效、灵活且通用的编程语言。它的设计目标是与操作系统良好交互,适用于系统编程和对性能有要求的领域。学习C语言可以为后续学习其他编程语言打下基础,并有助于开发者掌握底层细节和提高程序性能

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值