關於Windows下延時、計時的精度,我以前寫過一篇文字,給出了一種通過嵌入彙編代碼讀取CPU指令周期寄存器來達到毫秒級的延時、計時的方法。(見http://blog.csdn.net/qiqi5521/archive/2007/11/03/1864642.aspx文中分析了WindowsAPI的計時精度,闡述了指令周期計時法的原理,看本篇前建議先看這篇。不過當時代碼是作為一個VC++動態連結程式庫實現的,一直想把它改寫為一個C++類。今天終於動手了,起因源於wxWidgets論壇上有一個人在問這個問題,終於引起了我的興趣,於是克服了惰性,動了一把手。設計目標:在VC++和gcc下都能夠編譯。為了達到這個目標,首先要解決兩個問題:1、vc++和gcc對64位整形的聲明方法不一樣,vc++是__int64,gcc是long long 。2、VC++和gcc嵌入彙編的方法不一樣,gcc使用AT&T彙編,vc++使用intel彙編。為瞭解決這兩個問題,決定條件編譯,用 _MSC_VER 這個宏來判斷編譯環境是VC++還是gcc,如果有 _MSC_VER 宏,說明是VC++環境。
代碼如下:
asmdelay.h
----------------------------------------------------
//精確定時類。//功能1:擷取CPU主頻(MHz)//功能2:精確延時(毫秒級)//功能3:精確計時(毫秒級)//編譯: VC++ / gcc//注意: VC++下編譯,需要進入當前工程的Settings,選中asmdelay.cpp,選擇C/C++選項卡,從Category組合框中選中Precompiled Headers,選擇Not Using Precompiled headers。
#ifndef __ASMDELAY_h__24687444848486484894589754#define __ASMDELAY_h__24687444848486484894589754
#ifdef _MSC_VER typedef unsigned __int64 __UINT64;#else typedef unsigned long long __UINT64;#endif
class CAsmDelay{private: unsigned long m_Freq; //CPU主頻 __UINT64 m_TimeStamp; //計時器開啟時的CPU時間戳記 int GetFrequency(void) ; //計算CPU主頻的函數 __UINT64 GetCycleCount(); //讀取CPU的指令周期寄存器public: unsigned long GetCPUFreq(); //查看CPU主頻 CAsmDelay(); ~CAsmDelay(); void msDelay(long ms); //毫秒級延時 void StartCalculagraph(); //啟動計時器 long StopCalculagraph(); //結束計時器,並返回從啟動到關閉經過了多少毫秒。};
#endif
----------------------------------------------------
asmdelay.cpp
----------------------------------------------------
#include "asmdelay.h"#include "windows.h"
CAsmDelay::CAsmDelay(){ m_Freq = GetFrequency();}
CAsmDelay::~CAsmDelay(){}
unsigned long CAsmDelay::GetCPUFreq(){ return m_Freq;}
__UINT64 CAsmDelay::GetCycleCount()//讀取CPU的指令周期寄存器{ volatile __UINT64 CycleCount;
#ifdef _MSC_VER __asm RDTSC __asm mov DWORD PTR CycleCount, EAX __asm mov DWORD PTR (CycleCount+4), EDX return CycleCount;#else volatile unsigned long lo=0,hi=0, *phi=((unsigned long *)(&CycleCount)) + 1, *plo=(unsigned long *)&CycleCount;
__asm__ ("RDTSC"); __asm__ __volatile__ ("movl %%eax,%0; movl %%edx,%1;" :"=r"(lo),"=r"(hi)::"%eax","%edx");
*phi=hi; *plo=lo; return CycleCount;#endif
}
//擷取CPU頻率的內建函式 //MHzint CAsmDelay::GetFrequency(void){
_LARGE_INTEGER CurrTicks, TicksCount; __UINT64 iStartCounter, iStopCounter;
unsigned long dwOldProcessP = GetPriorityClass(GetCurrentProcess()); unsigned long dwOldThreadP = GetThreadPriority(GetCurrentThread());
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
QueryPerformanceFrequency(&TicksCount); QueryPerformanceCounter(&CurrTicks);
TicksCount.QuadPart /= 16; TicksCount.QuadPart += CurrTicks.QuadPart;
iStartCounter=GetCycleCount();
while(CurrTicks.QuadPart<TicksCount.QuadPart) QueryPerformanceCounter(&CurrTicks);
iStopCounter = GetCycleCount();
SetThreadPriority(GetCurrentThread(), dwOldThreadP); SetPriorityClass(GetCurrentProcess(), dwOldProcessP);
return (int)((iStopCounter-iStartCounter)/62500);
}
void CAsmDelay::msDelay(long ms){ //把毫秒數轉成刻度數 __UINT64 msecToCycle = ( m_Freq * 1000 ) * ms; //記錄當前刻度 __UINT64 tmr= GetCycleCount(); //延時,直到刻度數滿足預定的毫秒數; while(GetCycleCount()-tmr < msecToCycle) { }}
void CAsmDelay::StartCalculagraph() //啟動計時器{ m_TimeStamp = GetCycleCount();}long CAsmDelay::StopCalculagraph() //結束計時器,並返回從啟動到關閉經過了多少毫秒。{ __UINT64 TimeStampDiff = GetCycleCount()-m_TimeStamp; return TimeStampDiff / (m_Freq *1000);}
----------------------------------------------------
測試工程:
http://wxforum.shadonet.com/download.php?id=731
說明:實現毫秒級延時、計時的代碼。
編譯器相容性:VC++ / gcc
包含工程:.dev 用wxDev-C++可編譯
.dsw 用VC++6可編譯
兩個工程共用asmdelay.cpp .h
wxDev-C++和VC++程式運行介面:
-------------------------------------------------------------
附:gcc內嵌彙編的調試小經驗:
1、AT&T彙編和intel彙編有很大不同,AT&T彙編mov指令資料類型用加尾碼來實現,如movb movw movl
而且運算元是從左向右拷貝,和intel彙編相反。
2、 gcc通過格式化的字串來產生彙編代碼,如本文中所示:
__asm__ __volatile__ ("movl %%eax,%0; movl %%edx,%1;"
:"=r"(lo),"=r"(hi)::"%eax","%edx");
第一個部分是一個字串: "movl %%eax,%0; movl %%edx,%1;" ,是彙編指令,可以有多條彙編指令,用;分割
緊接著是:分隔字元,然後是第二部分,是輸出部(目標運算元),在這裡可以串連C語言變數,輸出部的各個元素用逗號分隔,第一個元素對應%0,第二個元素對應%1,依此類推。
"=r"中的=表示輸出,r表示連結寄存器變數,也可以用g表示可以是寄存器或記憶體變數,也可以用m表示記憶體變數,有__volatile__ 的時候它們效果是一樣的,具體情況參見5。
括弧中是串連的進階語言變數。
第三部分是輸入部(源運算元),由於輸入部沒有串連進階語言變數,所以省略了,兩個連續的::其實是由於省略了第三部分的緣故
最後一部分,第四部分,是限定寄存器,修飾第一部分中的寄存器。
3、如果gcc開啟了速度最佳化,那麼內嵌了彙編代碼的函數裡面的進階語言變數最好用 volatile 修飾,以保證運行結果的正確。否則的話,由於最佳化會導致
程式在寄存器中分配變數,而彙編往往會使用寄存器,就會不可避免的產生衝突而導致錯誤的結果。
4、__volatile__ 修飾符告訴編譯器,禁止最佳化。阻止編譯器刪除、重組彙編指令。
5、約束字母:
:"=r" (__dummy)
“=r”表示相應的目標運算元可以使用任何一個通用寄存器,並且變數__dummy 存放在這個寄存器中,但如果是:
:“=m”(__dummy)
“=m”就表示相應的目標運算元是存放在記憶體單元__dummy中。這裡面的r和m就叫做約束字母。
約束字母很多,下表給出幾個主要的約束字母及其含義:
| 字母 |
含義 |
| m, v,o |
表示記憶體單元 |
| R |
表示任何通用寄存器 |
| Q |
表示寄存器eax, ebx, ecx,edx之一 |
| I, h |
表示直接運算元 |
| E, F |
表示浮點數 |
| G |
表示“任意” |
| a, b.c d |
表示要求使用寄存器eax/ax/al, ebx/bx/bl, ecx/cx/cl或edx/dx/dl |
| S, D |
表示要求使用寄存器esi或edi |
| I |
表示常數(0~31) |