和其他Windows服務器一樣,在Windows 2003 Server上最能發揮性能優勢的是多線程程序。Windows 2003 Server支持各種多處理器系統,同時也能在單處理器的P4系統上運行。對于單處理器P4系統,Windows 2003 Server將發揮出Intel超線程技術提供的各種硬件線程執行引擎的優勢。
開發服務器應用的人都知道,之所以要開發并行程序,真正的原因只有一個——性能。然而,眾所周知,性能改善是一個比較模糊的目標,因為多線程代碼的性能通常只能靠經驗估計。在單線程程序中,性能改進程度一般可以精確地預知,例如減少了多少指令和延遲較高的操作,但多線程代碼不同,Windows平臺中線程調度是不確定的,也就是說,在Windows中應用程序可以要求調度程序運行線程,但調度程序何時(是否)運行線程則遠遠超出了應用程序代碼的控制范圍。
在測試性能時,開發者很快會遇到一個問題,這就是Windows內建的標準時鐘實在不夠精確,其可靠測量事件時間的解析度很難高于一秒,這樣,要確定一個代碼片段是否真正得到優化就很困難了。如果一定要用Windows的標準時鐘進行測試,必須利用循環讓代碼運行幾百萬次,才能獲得有效的時間數據。絕大多數情況下,使用這類循環意味著修改應用程序。
其實,還有更好的辦法,這就是Win32高解析度時鐘,涉及的函數有兩個:QueryPerformanceCount(),QueryPerformanceFrequency()。在Intel系統中,從P II開始,這些函數依賴于Pentium芯片內建的一個計數器。當一個Intel系統啟動時,一個64位的寄存器跟蹤著消逝的時鐘周期,這個計數器提供了解析度極高的計時設備。
整個64位寄存器都要用到。32 bit的整數大約能計數20億,對于當前每秒運行20-30億個周期的處理器,32 bit的計數器會在一秒或更少的時間內溢出,64 bit的計數器則能容納這些秒數的20億倍,按20億秒計算就是約63年——可以相信,這已經遠遠超出測量任何程序的要求了。
要對一個事件進行計時,只需獲得事件開始之前、結束之后的時鐘計數。下面的代碼不依賴于Win32(即,從C/C++直接訪問),稍后我們再看看操作系統提供的函數。我們首先定義一個數據結構,然后再來看填寫該結構的代碼:
typedef struct _BinInt32
{
__int32 i32[2];
} BigInt32;
typedef struct _BigInt64
{
__int64 i64;
} BigInt64;
typedef union _bigInt
{ BigInt32 int32val;
BigInt64 int64val;
} BigInt;
下面的代碼從操作系統獲得時鐘計數器的高位和低位,分別填寫__int64數據的兩個32 bit部分:
BigInt start_ticks, end_ticks;
_asm {
RDTSC
mov start_ticks.int32val.i32[0], eax
mov start_ticks.int32val.i32[4], edx
}
這段代碼能夠在Visual Studio .NET 2003中順利運行,在以前的C/C++編譯器中也應該沒有問題。RDTSC(ReaD Time Stamp Counter)是一個匯編指令,它的功能是把時間戳計數器的內容裝入EAX和EDX寄存器。執行上述代碼后,start_ticks就包含了完整的時鐘計數。再次調用上面的代碼,把start_ticks替換成end_ticks,再從end_ticks減去start_ticks,就得到了兩次調用期間流逝的時鐘周期。
要輸出這個_int64值,可以使用下面的printf()掩碼:
printf ( "Function used %I64Ld ticks\n",
end_ticks.int64val.i64 - start_ticks.int64val.i64 );
Win32函數QueryPerformanceCounter()的功能也大致相似,它唯一的參數是一個指向計數器變量的長指針。如果函數調用失敗,它返回0(實際上是FALSE)。然而,上面提供的代碼突破了Windows調用的黑箱,即使在非Windows的Intel系統上,也能發揮同樣的功能。
二、使用計時數據
如果要把時鐘計數轉換成時間,只要把時鐘計數除以CPU的時鐘頻率就可以了。不過,芯片上標準的GHz數據往往與實際運行的速度不同。如果要測試芯片的實際速度,除了Win32調用QueryPerformanceFrequency(),還有幾種非常好的工具軟件。這里要推薦兩種工具,首先是Intel自己的Processor Frequency ID Utility,可以從http://support.intel.com/support/ processors/tools/FrequencyID/FreqID.htm免費下載,它還能提供有關處理器的許多其他信息。另一個工具提供的信息更多,它就是wCPUID,可以從http://www.h-oda.com/免費下載。
這兩種工具都能夠測出精確的時鐘速度,用前面獲得的時鐘計數除以速度,就可以得到高精度的時間計數。QueryPerformanceFrequency()函數也只有一個長指針參數,出現錯誤時返回0或FALSE。
在如此高的時鐘解析度下,許多平?床坏降默F象會顯現出來。最令人莫名其妙的是,多次測試同一段代碼,結果會出現很大的波動。
大范圍波動的主要原因在于讀取操作,特別是第一、二兩次讀取與從緩沖區讀取的差異。當代碼第一次執行時,一般需要把它裝入到緩沖區,代碼所操作的數據也一樣。用時鐘周期來度量,這一緩沖裝入過程是相當耗時的。不過,當代碼和數據放入了緩沖區(多次運行代碼之后的結果),裝入緩沖區操作所帶來的失真漸漸消失。因此,實際測試時,應當拋棄前幾次的數據,只計算結果穩定下來之后的平均值。
然而,即使在看起來比較穩定的結果集中,仍會突然出現一些突變,這是由于操作系統切換線程所導致的。由于時鐘計數器總是不停地累加,它的計數不能反映出代碼的一部分執行時間已經用于休眠。要解決這個問題,必須將線程設置成Windows最高的優先級,即實時(對應的符號是REALTIME_PRIORITY_CLASS),防止測試期間線程被切換掉。
但是,采用這種解決辦法時必須謹慎。如果讓一大段代碼用這個優先級運行,可能會阻塞其他線程。因此,如果要用這種辦法測試大段代碼,應當確信系統暫時不作它用。另外,必須記住的是,測試完成后要把代碼恢復成標準優先級——注意,是在測試完成后立即恢復,否則的話,可能帶來許多風險,例如,可能直到部署應用程序時也不能再想起需要恢復優先級,由此帶來的問題可能使用戶久久難忘——當你的應用程序開始運行時,其他代碼都好像停止運行了。
當然,這是一個可以暫且不管的話題。無論怎樣,現在我們已經有了一個高度精確的時鐘,它能夠在大多數當前的Intel處理器上運行,適合從Windows 95開始的所有Windows操作系統。好好享受吧!
延伸閱讀
文章來源于領測軟件測試網 http://www.kjueaiud.com/