聲明:本文章是我整合網上的資料而成的,其中的大部分文字不是我所爲的,我所起的做用只是概括整理並添加個人一些見解。很是感謝引用到的文字的做者的辛勤勞動,所參考的文獻在文章最後我已一一列出。html
對關注性能的程序開發人員而言,一個好的計時部件既是益友,也是良師。計時器既能夠做爲程序組件幫助程序員精確的控制程序進程,又是一件有力的調試武器,在有經驗的程序員手裏能夠儘快的肯定程序的性能瓶頸,或者對不一樣的算法做出有說服力的性能比較。
在Windows平臺下,經常使用的計時器有兩種,一種是timeGetTime多媒體計時器,它能夠提供毫秒級的計時。但這個精度對不少應用場合而言仍是太粗糙了。另外一種是QueryPerformanceCount計數器,隨系統的不一樣能夠提供微秒級的計數。對於實時圖形處理、多媒體數據流處理、或者實時系統構造的程序員,善用QueryPerformanceCount/QueryPerformanceFrequency是一項基本功。
本文要介紹的,是另外一種直接利用Pentium CPU內部時間戳進行計時的高精度計時手段。如下討論主要得益於《Windows圖形編程》一書,第 15頁-17頁,有興趣的讀者能夠直接參考該書。關於RDTSC指令的詳細討論,能夠參考Intel產品手冊。本文僅僅做拋磚之用。
在 Intel Pentium以上級別的CPU中,有一個稱爲「時間戳(Time Stamp)」的部件,它以64位無符號整型數的格式,記錄了自CPU上電以來所通過的時鐘週期數。因爲目前的CPU主頻都很是高,所以這個部件能夠達到納秒級的計時精度。這個精確性是上述兩種方法所沒法比擬的。
在Pentium以上的CPU中,提供了一條機器指令RDTSC(Read Time Stamp Counter)來讀取這個時間戳的數字,並將其保存在EDX:EAX寄存器對中。因爲EDX:EAX寄存器對剛好是Win32平臺下C++語言保存函數返回值的寄存器,因此咱們能夠把這條指令當作是一個普通的函數調用。像這樣:
inline unsigned __int64 GetCycleCount()
{
__asm RDTSC
}
可是不行,由於RDTSC不被C++的內嵌彙編器直接支持,因此咱們要用_emit僞指令直接嵌入該指令的機器碼形式0X0F、0X31,以下:
inline unsigned __int64 GetCycleCount()
{
__asm _emit 0x0F
__asm _emit 0x31
}
之後在須要計數器的場合,能夠像使用普通的Win32 API同樣,調用兩次GetCycleCount函數,比較兩個返回值的差,像這樣:
unsigned long t;
t = (unsigned long)GetCycleCount();
//Do Something time-intensive ...
t -= (unsigned long)GetCycleCount();
《Windows圖形編程》第15頁編寫了一個類,把這個計數器封裝起來。有興趣的讀者能夠去參考那個類的代碼。做者爲了更精確的定時,作了一點小小的改進,把執行RDTSC指令的時間,經過連續兩次調用GetCycleCount函數計算出來並保存了起來,之後每次計時結束後,都從實際獲得的計數中減掉這一小段時間,以獲得更準確的計時數字。但我我的以爲這一點點改進意義不大。在個人機器上實測,這條指令大概花掉了幾十到100多個週期,在 Celeron 800MHz的機器上,這不過是十分之一微秒的時間。對大多數應用來講,這點時間徹底能夠忽略不計;而對那些確實要精確到納秒數量級的應用來講,這個補償也過於粗糙了。
程序員
我從《Windows圖形編程》上把這個類的源碼拷貝了下來供你們看看,下面是使用RDTSC指令的CPU時鐘循環秒錶類:web
這個方法的優勢是:
1.高精度。能夠直接達到納秒級的計時精度(在1GHz的CPU上每一個時鐘週期就是一納秒),這是其餘計時方法所難以企及的。
2. 成本低。timeGetTime 函數須要連接多媒體庫winmm.lib,QueryPerformance* 函數根據MSDN的說明,須要硬件的支持(雖然我尚未見過不支持的機器)和KERNEL庫的支持,因此兩者都只能在Windows平臺下使用(關於DOS平臺下的高精度計時問題,能夠參考《圖形程序開發人員指南》,裏面有關於控制定時器8253的詳細說明)。但RDTSC指令是一條CPU指令,凡是i386平臺下Pentium以上的機器均支持,甚至沒有平臺的限制(我相信i386版本UNIX和Linux下這個方法一樣適用,但沒有條件試驗),並且函數調用的開銷是最小的。算法
(這裏我想說的是:照這樣看,跨平臺也只能說是操做系統平臺,不能跨硬件平臺,就是說只能用在Intel Pentium以上的機器)編程
3. 具備和CPU主頻直接對應的速率關係。一個計數至關於1/(CPU主頻Hz數)秒,這樣只要知道了CPU的主頻,能夠直接計算出時間。這和 QueryPerformanceCount不一樣,後者須要經過QueryPerformanceFrequency獲取當前計數器每秒的計數次數才能換算成時間。
這個方法的缺點是:
1.現有的C/C++編譯器多數不直接支持使用RDTSC指令,須要用直接嵌入機器碼的方式編程,比較麻煩。
2.數據抖動比較厲害。其實對任何計量手段而言,精度和穩定性永遠是一對矛盾。若是用低精度的timeGetTime來計時,基本上每次計時的結果都是相同的;而RDTSC指令每次結果都不同,常常有幾百甚至上千的差距。這是這種方法高精度自己固有的矛盾。windows
(這裏數據抖動確實是一個大問題,我遇到過這樣一種狀況,好比測試a和b兩種算法,因爲數據抖動,有時a比b耗時少,有時b比a耗時少。我想過兩種測試辦法:ide
(1)增多測試次數,好比對a和b兩種算法各測試10次,看a比b耗時少的次數和b比a耗時少的次數哪一個多,以此斷定哪一個算法效率高。函數
(2)增大測試數據量,我想一增大測試數據量,算法效率的差別就會顯現出來)
關於這個方法計時的最大長度,咱們能夠簡單的用下列公式計算:
自CPU上電以來的秒數 = RDTSC讀出的週期數 / CPU主頻速率(Hz)
64位無符號整數所能表達的最大數字是1.8×10^19,在個人Celeron 800上能夠計時大約700年(書中說能夠在200MHz的Pentium上計時117年,這個數字不知道是怎麼得出來的,與個人計算有出入)。不管如何,咱們大可沒必要關心溢出的問題。
性能
下面是幾個小例子,簡要比較了三種計時方法的用法與精度測試
//以上三個示例程序都是測試1秒鐘休眠所耗費的時間
file://測/試環境:Celeron 800MHz / 256M SDRAM
// Windows 2000 Professional SP2
// Microsoft Visual C++ 6.0 SP5
////////////////////////////////////////////////
如下是Timer1的運行結果,使用的是高精度的RDTSC指令
Lasting Time: 804586872
如下是Timer2的運行結果,使用的是最粗糙的timeGetTime API
Begin Time: 20254254
End Time: 20255255
Lasting Time: 1001
如下是Timer3的運行結果,使用的是QueryPerformanceCount API
Frequency: 3579545
Begin Time: 3804729124
End Time: 3808298836
Lasting Time: 3569712
古人說,舉一反三。從一本介紹圖形編程的書上獲得一個如此有用的實時處理知識,我感到很是高興。有美不敢自專,但願你們和我同樣喜歡這個輕便有效的計時器。
網上有一種說法說
double dTotalTime=(double)(t2.QuadPart-t1.QuadPart)/(double)tc.QuadPart
可能有問題,好比說如今不少主板都有CPU頻率自動調整功能,主要是節能,尤爲在筆記本上,這樣除下來不能保證精確性。我不肯定這種說法是否準確,供你們研究
上文主要摘自《使用CPU時間戳進行高精度計時》,其實除了上面提到的三種方法,還有一種經常使用固然沒有上面準確的辦法,就是使用GetTickCount函數,這種方法可以獲取毫秒級的時間,具體用法以下:
參考文獻:
《使用CPU時間戳進行高精度計時》 做者:zhangyan_qd
《Windows圖形編程》,(美)Feng Yuan 著
《VC中取得毫秒級的時間》,http://www.cppblog.com/humanchao/archive/2008/04/22/43322.html