Windowsのタイマー

7036 ワード

出発点:画像処理アルゴリズムの最適化の過程で、比較的正確なタイミングでコードの効率を測定する必要がある.
組み込みプラットフォーム(単片機、DSP、ARM)では、遅延などのタイミング操作がよく用いられ、正確に行うことができる.Windowsでは、あるいはPCでも、オペレーティングシステムが向上したAPIを利用してタイミング操作を実現することができますが、精度が高くはできません.
周知のように、Windowsはメッセージメカニズムに基づくシステムであり、いかなるイベントの実行もメッセージの送信と受信によって行われる.これにより、コンピュータのCPUがプロセスによって占有されたり、システムリソースが緊張したりすると、メッセージキューに送信されたメッセージが一時的に保留され、リアルタイム処理が得られないなどの問題が発生する.したがって、Windowsメッセージを介してタイミングに厳しいイベントを簡単に開始することはできない.また,Windowsでは既にコンピュータの下位ハードウェアへのアクセスがカプセル化されているため,アクセスハードウェアを直接利用して正確なタイミングを達成することも困難である.従って、実用化の際には、具体的なタイミング精度の要求に応じて、適応したタイミング方法を採用しなければならない.
VCには時間動作に関する多くの関数が提供されており,それらの制御プログラムによりタイミング動作とタイミング動作を正確に完了することができる.本稿では,VCにおけるWindowsベースの正確なタイミングの7つの方式を詳細に紹介した.
方式一:VC中のWM_TIMERメッセージマッピングは簡単な時間制御が可能
まず、関数SetTimer()を呼び出して、SetTimer(0200,NULL)が200 msの時間間隔を設定するようにタイミング間隔を設定する.その後、アプリケーションにタイミング応答関数OnTimer()を追加し、到着タイミングの操作を完了するために応答の処理文を追加します.このようなタイミング方法は非常に簡単で、一定のタイミング機能を実現することができるが、そのタイミング機能はSleep()関数の遅延機能のように、精度が非常に低く、最小タイミング精度が30 msしかなく、CPU占有量が低く、タイマメッセージのマルチタスクオペレーティングシステムにおける優先度が低く、タイムリーな応答が得られず、リアルタイム制御環境での応用を満たすことができないことが多い.ビットマップのダイナミック表示など、タイミング精度が要求されない場合にのみ使用できます.
方式2:VCでsleep()関数を用いて遅延を実現する
その単位はmsであり、例えば2秒遅延し、sleep(2000)を用いる.精度は非常に低く、最小計時精度はわずか30 msであり、sleep関数の不利な点は、遅延期間で他のメッセージを処理できないことであり、時間が長すぎると、デッドラインのようにCPU占有率が非常に高く、要求の低い遅延プログラムにしか使用できないことである.
方式3:COleDateTimeクラスとCOleDateTimeSpanクラスをWINDOWSのメッセージ処理プロセスと組み合わせて秒級遅延を実現する.
以下は、2秒を実現するための遅延コードです.
COleDateTime      start_time = COleDateTime::GetCurrentTime();
COleDateTimeSpan  end_time= COleDateTime::GetCurrentTime()-start_time;
while(end_time.GetTotalSeconds()< 2) //    2 
{
MSG   msg;
GetMessage(&msg,NULL,0,0);
TranslateMessage(&msg);
DispatchMessage(&msg);
//                       ,
  //        CPU    ,
//           ,         。
end_time = COleDateTime::GetCurrentTime()-start_time;
}//                    。

方式四:GetTickCount()関数
精度の要求が高い場合、VCではGetTickCount()関数を利用することができ、この関数の戻り値はDWORD型であり、ms単位のコンピュータ起動後に経験した時間間隔を表す.精度比WM_TIMERメッセージマッピングが高く、短いタイミングでは計時誤差が15 ms、長いタイミングでは計時誤差が低く、タイミング時間が長すぎるとデッドラインのようにCPU占有率が非常に高く、要求の低い遅延プログラムにしか使用できない.次のコードは、50 msの正確なタイミングを実現します.
DWORD dwStart = GetTickCount();
DWORD dwEnd   = dwStart;
do
{
dwEnd = GetTickCount()-dwStart;
}while(dwEnd <50);
GetTickCount()関数が遅延またはタイミングの間に他のメッセージを処理できるように、コードを次のように変更できます.
DWORD dwStart = GetTickCount();
DWORD dwEnd   = dwStart;
do
{
MSG   msg;
GetMessage(&msg,NULL,0,0);
TranslateMessage(&msg);
DispatchMessage(&msg);
dwEnd = GetTickCount()-dwStart;
}while(dwEnd <50);
は、このようにしてCPUの占有率を低減することができ、遅延またはタイミング期間中に他のメッセージを処理することもできるが、遅延またはタイミング精度を低減することができる.
方式5:マルチメディアタイマ関数DWORD timeGetTime(void)
この関数のタイミング精度はmsレベルで、Windowsの起動から経過したミリ秒数を返します.マイクロソフト社は、マルチメディアWindowsで正確なタイマの下位APIを提供し、マルチメディアタイマを利用してシステムの現在の時間を正確に読み出し、非常に正確な時間間隔でイベント、関数、またはプロセスの呼び出しを完了することができます.DWORD timeGetTime(void)関数を呼び出す前にWinmmをlibとMmsystemhプロジェクトに追加しないと、コンパイル時にDWORD timeGetTime(void)関数が定義されていないことを示す.この関数を使用すると、クエリーによってタイミング制御が行われるので、タイミングイベントの制御を行うためにタイミングサイクルを確立する必要があります.
方式六:マルチメディアタイマーtimeSetEvent()関数を使用する
この関数のタイミング精度はmsレベルである.この関数を使用すると、周期的な関数呼び出しを実現できます.関数のプロトタイプは次のとおりです.
MMRESULT timeSetEvent( UINT uDelay,
UINT uResolution,
LPTIMECALLBACK lpTimeProc,
WORD dwUser,
UINT fuEvent )
この関数は、1回のイベントまたは周期的なイベントであることができるタイミングコールバックイベントを設定します.イベントがアクティブになると、指定したコールバック関数が呼び出され、成功するとイベントの識別子コードが返されます.そうしないとNULLが返されます.関数のパラメータの説明は次のとおりです.
uDelay:          。
Uresolution:          ,              。    1ms。
LpTimeProc:        。
DwUser:           。
FuEvent:         :
TIME_ONESHOT:uDelay          
TIME_PERIODIC :  uDelay          。

具体的に適用する場合、timeSetEvent()関数を呼び出すことで、周期的に実行する必要があるタスクをLpTimeProcコールバック関数(タイミングサンプリング、制御など)に定義し、処理するイベントを完了することができます.タスク処理の時間は、サイクル間隔時間より大きくてはならないことに注意してください.また、タイマーの使用が完了したら、timeKillEvent()を呼び出して解放します.
方式7:QueryPerformanceFrequency()とQueryPerformanceCounter()関数
精度が最も高い方法.この2つの関数は、VCが提供するWindows 95以降のバージョンでのみ使用できる正確な時間関数であり、コンピュータにハードウェアから正確なタイマをサポートするように要求しています(現在はサポートされています).
QueryPerformanceFrequency()関数とQueryPerformanceCounter()関数のプロトタイプは次のとおりです.
BOOL  QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
BOOL  QueryPerformanceCounter(LARGE_INTEGER *lpCount);
データ型ARGE_INTEGERは、コンパイラが64ビットをサポートするか否かに応じて、8バイト長の整数であってもよいし、2つの4バイト長の整数であってもよい.このタイプの定義は次のとおりです.
typedef union _LARGE_INTEGER
{
struct
{
DWORD LowPart ;// 4     
LONG  HighPart;// 4     
};
LONGLONG QuadPart ;// 8     
}LARGE_INTEGER ;
タイミングを行う前に、まずQueryPerformanceFrequency()関数を呼び出して機器内部タイマのクロック周波数を取得し、次に厳格なタイミングが必要なイベントが発生する前と発生した後にそれぞれQueryPerformanceCounter()関数を呼び出し、2回の得られたカウントの差とクロック周波数を利用して、イベントが経験する正確な時間を計算する(単片機のタイマーを使ったことがあるので、この過程を理解しやすく、タイミングパルスの個数を数える).次のコードは1 msの正確なタイミングを実現します.
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;//           
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;//      
do
{
QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;//     
dfMinus = (double)(QPart2-QPart1);
dfTim = dfMinus / dfFreq;//         ,    
}while(dfTim<0.001);

そのタイミング誤差は1マイクロ秒を超えず,精度はCPUなどの機器配置に関係する.次のプログラムは、関数Sleep(100)の正確な持続時間をテストするために使用されます.
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;//           
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;//      
Sleep(100);
QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;//     
dfMinus = (double)(QPart2-QPart1);
dfTim = dfMinus / dfFreq;//         ,    

Sleep()関数自体の誤差により,上記のプログラムは実行するたびに微小な誤差が生じる.次のコードは、1マイクロ秒の正確なタイミングを実現します.
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;//           
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;//      
do
{
QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;//     
dfMinus = (double)(QPart2-QPart1);
dfTim = dfMinus / dfFreq;//         ,    
}while(dfTim<0.000001);
そのタイミング誤差は一般に0.5マイクロ秒を超えず、精度はCPUなどの機器構成に関係する.
使い方をまとめます.
a.パラメータの定義
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
b.CPUのタイマ周波数を取得する
QueryPerformanceFrequency(&litmp);
dfFreq = (double)litmp.QuadPart;//           
c.時点0
QueryPerformanceCounter(&litmp);
QPart1 = litmp.QuadPart;//      
d.時点1
QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;//     
e.計算時間
dfMinus = (double)(QPart2-QPart1);
dfTim = dfMinus / dfFreq;//         ,