上次我對C#類與結構體做了一次速度評測(http://blog.csdn.net/zyl910/article/details/6788417)。經過一段時間思索,發現還可以進一步探討——
第一、棧變量。上次的“硬編碼”,是訪問類中的靜態變量的。若改爲訪問函數中的棧變量,性能會不會有所提高?第二、棧分配(stackalloc)。既然要測試棧變量,我們還可以順便測試一下在棧上分配的內存塊的訪問性能。
第三、64位整數。由於32位系統的成功,我們已經習慣了使用32位整數(int)。現在64位系統逐漸普及,我們得爲此做好準備。對於指針操作時經常要用到的偏移量增減運算來說,是使用32位整數,還是使用64位整數,或寫兩套代碼?這需要測試後才能決定。
第四、密封類(sealed)。聽說密封類能提高性能,我們可以測試一下。有兩種測試方式,一是爲原來的派生類增加sealed關鍵字,二是專門另外寫一個密封類。我決定同時使用這兩種方法,分別測試其性能。
一、測試代碼
測試代碼如下——
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 sealed class PointerCallAdd : PointerCall
{
/// <summary>
/// 偏移值
/// </summary>
public int Offset = 0;
public override unsafe byte* Ptr(byte* p)
{
return unchecked(p + Offset);
}
}
/// <summary>
/// [非泛型] 指針操作密封類: 指針+偏移
/// </summary>
public sealed class SldPointerCallAdd : IPointerCall
{
/// <summary>
/// 偏移值
/// </summary>
public int Offset = 0;
public 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
{
#if DEBUG
private const int CountLoop = 10000000; // 循環次數
#else
private const int CountLoop = 200000000; // 循環次數
#endif
/// <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, int CountLoop)
{
TryIt_Static_Offset = 1;
// == 性能測試 ==
byte* p = null;
Stopwatch sw = new Stopwatch();
int i;
unchecked
{
#region 測試
// 硬編碼.棧變量
int iOffset = 1;
sw.Reset();
sw.Start();
for (i = 0; i < CountLoop; ++i)
{
p = p + iOffset;
}
sw.Stop();
sOut.AppendLine(string.Format("硬編碼.棧變量:\t{0}", sw.ElapsedMilliseconds));
// 硬編碼.棧分配
int* pOffset = stackalloc int[1];
pOffset[0] = 1;
sw.Reset();
sw.Start();
for (i = 0; i < CountLoop; ++i)
{
p = p + pOffset[0];
}
sw.Stop();
sOut.AppendLine(string.Format("硬編碼.棧分配:\t{0}", sw.ElapsedMilliseconds));
// 硬編碼.靜態
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 long TryIt_Static64_Offset;
private static unsafe byte* TryIt_Static64_Ptr(byte* p)
{
return unchecked(p + TryIt_Static64_Offset);
}
/// <summary>
/// 執行測試 - 靜態調用
/// </summary>
/// <param name="sOut">文本輸出</param>
private static unsafe void TryIt_Static64(StringBuilder sOut, int CountLoop)
{
TryIt_Static64_Offset = 1;
// == 性能測試 ==
byte* p = null;
Stopwatch sw = new Stopwatch();
int i;
unchecked
{
#region 測試
// 硬編碼.棧變量
long iOffset = 1;
sw.Reset();
sw.Start();
for (i = 0; i < CountLoop; ++i)
{
p = p + iOffset;
}
sw.Stop();
sOut.AppendLine(string.Format("64硬編碼.棧變量:\t{0}", sw.ElapsedMilliseconds));
// 硬編碼.棧分配
long* pOffset = stackalloc long[1];
pOffset[0] = 1;
sw.Reset();
sw.Start();
for (i = 0; i < CountLoop; ++i)
{
p = p + pOffset[0];
}
sw.Stop();
sOut.AppendLine(string.Format("64硬編碼.棧分配:\t{0}", sw.ElapsedMilliseconds));
// 硬編碼.靜態
sw.Reset();
sw.Start();
for (i = 0; i < CountLoop; ++i)
{
p = p + TryIt_Static64_Offset;
}
sw.Stop();
sOut.AppendLine(string.Format("64硬編碼.靜態:\t{0}", sw.ElapsedMilliseconds));
// 靜態調用
sw.Reset();
sw.Start();
for (i = 0; i < CountLoop; ++i)
{
p = TryIt_Static64_Ptr(p);
}
sw.Stop();
sOut.AppendLine(string.Format("64靜態調用:\t{0}", sw.ElapsedMilliseconds));
#endregion // 測試
}
}
/// <summary>
/// 執行測試 - 非泛型
/// </summary>
/// <param name="sOut">文本輸出</param>
private static unsafe void TryIt_NoGen(StringBuilder sOut, int CountLoop)
{
// 創建
PointerCallAdd pca = new PointerCallAdd();
SldPointerCallAdd dpca = new SldPointerCallAdd();
SPointerCallAdd spca;
pca.Offset = 1;
spca.Offset = 1;
// 轉型
PointerCall pca_base = pca;
IPointerCall pca_itf = pca;
IPointerCall dpca_itf = dpca;
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 = dpca.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 = dpca_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(dpca, 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(dpca_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, int CountLoop)
{
// !!! C#不支持將整數類型作爲泛型約束 !!!
}
/// <summary>
/// 執行測試
/// </summary>
public static string TryIt()
{
StringBuilder sOut = new StringBuilder();
sOut.AppendLine("== PointerCallTool.TryIt() ==");
TryIt_Static(sOut, CountLoop);
TryIt_Static64(sOut, CountLoop);
TryIt_NoGen(sOut, CountLoop);
TryIt_Gen(sOut, CountLoop);
sOut.AppendLine();
return sOut.ToString();
}
/// <summary>
/// 執行測試 - static
/// </summary>
public static string TryItStatic()
{
StringBuilder sOut = new StringBuilder();
int cnt = CountLoop * 10;
sOut.AppendLine("== PointerCallTool.TryItStatic() ==");
TryIt_Static(sOut, cnt);
TryIt_Static64(sOut, cnt);
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 XP SP3 32位。
B_2010:機器B,VS2010,Window XP SP3 32位。
B64_2005:機器B,VS2005,Window 7 64位(x64)。
B64_2010:機器B,VS2010,Window 7 64位(x64)。
三、硬編碼與靜態調用 的測試結果(棧變量、棧分配、64位整數)
因爲硬編碼與靜態調用很可能會被執行函數展開優化,速度明顯比其他測試項目要快。所我另外寫了一個測試函數(TryItStatic),將循環次數設爲原來的10倍。
測試結果如下(單位:毫秒)——
模式 | A_2005 | A_2010 | B_2005 | B_2010 | B64_2005 | B64_2010 |
---|---|---|---|---|---|---|
硬編碼.棧變量: | 1608 | 1623 | 957 | 966 | 960 | 959 |
硬編碼.棧分配: | 1612 | 1617 | 1073 | 957 | 961 | 960 |
硬編碼.靜態: | 1609 | 1613 | 957 | 971 | 961 | 960 |
靜態調用: | 1608 | 1611 | 1063 | 958 | 961 | 963 |
64硬編碼.棧變量: | 1610 | 1617 | 967 | 957 | 959 | 1010 |
64硬編碼.棧分配: | 1610 | 1619 | 1034 | 957 | 960 | 1012 |
64硬編碼.靜態: | 1609 | 1618 | 999 | 996 | 957 | 1010 |
64靜態調用: | 1610 | 1615 | 959 | 1002 | 957 | 7696 |
結果分析——
先看32位與64位的區別。發現在大多數情況,32位與64位的速度是一樣的。唯一就是64位整數運算代碼在“64位平臺+VS2010”上運行時,速度比在32位下還慢,尤其是靜態調用慢了好幾倍,硬編碼代碼的速度也有所下降。真的很奇怪,既然運行的是同一份程序,爲什麼64位比32位還慢,難道是.Net 4.0在x64平臺上的即時編譯器的問題?不解。
棧變量、棧分配、靜態變量的訪問速度幾乎一致,看來可以放心地隨意使用。
看來以後寫指針操作代碼時,只寫64位整數版就行了。
四、密封類 的測試結果
測試結果如下(單位:毫秒)——
模式 A_2005 A_2010 B_2005 B_2010 B64_2005B64_2010
硬編碼.棧變量: 162 162 95 95 96 95
硬編碼.棧分配: 161 161 95 95 95 97
硬編碼.靜態: 161 165 97 95 97 95
靜態調用: 161 163 95 95 96 97
64硬編碼.棧變量: 161161989596100
64硬編碼.棧分配: 160162959795100
64硬編碼.靜態: 162 162 95 97 95 100
64靜態調用: 161 161 95 95 97 770
調用派生類: 563 568 670 668 676 580
調用密封類: 161 162 101 103 102 767
調用結構體: 163 161 116 102 191 772
調用基類: 566 573 668 660 675 577
調用派生類的接口: 727 731 767 862 862 770
調用密封類的接口: 721 730 957 862 870 771
調用結構體的接口: 104511341318134013441253
基類泛型調用派生類: 910795127478912561287
基類泛型調用基類: 902 785 1092 676 1346 1250
接口泛型調用派生類: 1407733163486216331633
接口泛型調用密封類: 1405808173395617431638
接口泛型調用結構體: 5661606711018641250
接口泛型調用結構體引用: 48016170098769961
接口泛型調用基類: 1409728176776416311635
接口泛型調用派生類的接口: 1410727170296617301634
接口泛型調用密封類的接口: 1402808171995816351637
接口泛型調用結構體的接口: 161711281859149922082117
將測試結果重新排版一下,突出不同實現方法的速度區別——
環境 | 分類 | 基類 | 派生類 | 密封類 | 結構體 | 結構體的引用 |
---|---|---|---|---|---|---|
A_2005 | 直接調用 | 566 | 563 | 161 | 163 | |
接口調用 | 727 | 721 | 1045 | |||
基類約束泛型調用 | 902 | 910 | ||||
接口約束泛型調用 | 1407 | 1405 | 566 | 480 | ||
接口約束泛型調用接口 | 1409 | 1410 | 1402 | 1617 | ||
A_2010 | 直接調用 | 573 | 568 | 162 | 161 | |
接口調用 | 731 | 730 | 1134 | |||
基類約束泛型調用 | 785 | 795 | ||||
接口約束泛型調用 | 733 | 808 | 160 | 161 | ||
接口約束泛型調用接口 | 728 | 727 | 808 | 1128 | ||
B_2005 | 直接調用 | 668 | 670 | 101 | 116 | |
接口調用 | 767 | 957 | 1318 | |||
基類約束泛型調用 | 1092 | 1274 | ||||
接口約束泛型調用 | 1634 | 1733 | 671 | 700 | ||
接口約束泛型調用接口 | 1767 | 1702 | 1719 | 1859 | ||
B_2010 | 直接調用 | 660 | 668 | 103 | 102 | |
接口調用 | 862 | 862 | 1340 | |||
基類約束泛型調用 | 676 | 789 | ||||
接口約束泛型調用 | 862 | 956 | 101 | 98 | ||
接口約束泛型調用接口 | 764 | 966 | 958 | 1499 | ||
B64_2005 | 直接調用 | 675 | 676 | 102 | 191 | |
接口調用 | 862 | 870 | 1344 | |||
基類約束泛型調用 | 1346 | 1256 | ||||
接口約束泛型調用 | 1633 | 1743 | 864 | 769 | ||
接口約束泛型調用接口 | 1631 | 1730 | 1635 | 2208 | ||
B64_2010 | 直接調用 | 577 | 580 | 767 | 772 | |
接口調用 | 770 | 771 | 1253 | |||
基類約束泛型調用 | 1250 | 1287 | ||||
接口約束泛型調用 | 1633 | 1638 | 1250 | 961 | ||
接口約束泛型調用接口 | 1635 | 1634 | 1637 | 2117 |
綜合來看,密封類的性能最好,在大多數測試項目中名列前茅——
“直接調用”時能被內聯(inline)優化,與“硬編碼”一樣快,快於派生類。
“接口調用”、“泛型調用接口”時與派生類性能一致,快於結構體的“接口調用”。
唯一就是在“泛型調用”時,落後於結構體,與派生類差不多稍微慢一點。
再就是奇怪的“64位平臺+VS2010”問題,密封類、結構體在直接調用時,還不如派生類。
最後總結一下可能會被內聯優化的調用類型——
32位平臺+VS2005:調用密封類、調用結構體。
32位平臺+VS2010:調用密封類、調用結構體、接口約束泛型調用結構體。
64位平臺+VS2005:調用密封類、調用結構體。
64位平臺+VS2010:(無)。
(完)
測試程序exe——
http://115.com/file/e6ymd5fe
http://download.csdn.net/detail/zyl910/3619643
源代碼下載——
http://115.com/file/aqz167n9
http://download.csdn.net/detail/zyl910/3619647
目錄——
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