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_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://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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章