[セットトップ]C++高精度タイマー


C++高精度実現計算プログラム実行時間
VCにおけるWindowsに基づく正確なタイミング中国科学院光電技術研究所遊志宇
工業生産制御システムでは、現在の時間をタイミング表示したり、画面上の進捗バーをタイミングリフレッシュしたり、上位機がタイミングを決めて下位機にコマンドを送信したり、データを転送したりするなど、タイミングを決めて完了する必要がある操作が多い.特に、制御性能に要求されるリアルタイム制御システムおよびデータ収集システムでは、より正確なタイミング操作が必要である.周知のように、Windowsはメッセージメカニズムに基づくシステムであり、いかなるイベントの実行もメッセージの送信と受信によって行われる.これにより、コンピュータのCPUがプロセスによって占有されたり、システムリソースが緊張したりすると、メッセージキューに送信されたメッセージが一時的に保留され、リアルタイム処理が得られないなどの問題が発生する.したがって、Windowsメッセージを介してタイミングに厳しいイベントを簡単に開始することはできない.また,Windowsでは既にコンピュータの下位ハードウェアへのアクセスがカプセル化されているため,アクセスハードウェアを直接利用して正確なタイミングを達成することも困難である.従って、実用化の際には、具体的なタイミング精度の要求に応じて、適応したタイミング方法を採用しなければならない.VCには時間動作に関する多くの関数が提供されており,それらの制御プログラムによりタイミング動作とタイミング動作を正確に完了することができる.本稿では、VCにおけるWindowsベースの正確なタイミングの7つの方式について詳しく説明し、下図に示すように、図1の画像記述方式1:VCにおけるWM_TIMERメッセージマッピングは簡単な時間制御が可能である.まず、関数SetTimer()を呼び出して、SetTimer(0200,NULL)が200 msの時間間隔を設定するようにタイミング間隔を設定する.その後、アプリケーションにタイミング応答関数OnTimer()を追加し、到着タイミングの操作を完了するために応答の処理文を追加します.このようなタイミング方法は非常に簡単で、一定のタイミング機能を実現することができるが、そのタイミング機能はSleep()関数の遅延機能のように、精度が非常に低く、最小タイミング精度が30 msしかなく、CPU占有量が低く、タイマメッセージのマルチタスクオペレーティングシステムにおける優位レベルが低く、タイムリーな応答が得られず、リアルタイム制御環境での応用を満たすことができないことが多い.ビットマップのダイナミック表示など、タイミング精度が要求されない場合にのみ使用できます.サンプルエンジニアリングのTimer 1のように.方式2:VCではsleep()関数を用いて遅延を実現し,その単位はms,例えば2秒遅延,sleep(2000)である.精度は非常に低く、最小計時精度はわずか30 msであり、sleep関数の不利な点は、遅延期間で他のメッセージを処理できないことであり、時間が長すぎると、デッドラインのようにCPU占有率が非常に高く、要求の低い遅延プログラムにしか使用できないことである.サンプルエンジニアリングのTimer 2のように.方式3:COleDateTimeクラスとCOleDateTimeSpanクラスをWINDOWSのメッセージ処理プロセスと組み合わせて秒級遅延を実現する.サンプルエンジニアリングのTimer 3とTimer 3_のように1.以下は、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;
      }//                    。

方式4:精度要求が高い場合、VCではGetTickCount()関数を利用することができ、この関数の戻り値はDWORD型であり、ms単位のコンピュータ起動後に経験した時間間隔を表す.精度比WM_TIMERメッセージマッピングが高く、短いタイミングでは計時誤差が15 ms、長いタイミングでは計時誤差が低く、タイミング時間が長すぎるとデッドラインのようにCPU占有率が非常に高く、要求の低い遅延プログラムにしか使用できない.サンプルエンジニアリングのTimer 4とTimer 4_のように1.次のコードは、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:GetTickCount()関数と同様のマルチメディアタイマ関数DWORD timeGetTime(void)は、タイミング精度がmsレベルであり、Windows起動から経過したミリ秒数を返す.マイクロソフト社は、マルチメディアWindowsで正確なタイマの下層APIを提供し、マルチメディアタイマを利用してシステムの現在の時間を正確に読み出し、非常に正確な時間間隔でイベント、関数、またはプロセスの呼び出しを完了することができます.DWORD timeGetTime(void)関数を呼び出す前にWinmmをlibとMmsystemhプロジェクトに追加しないと、コンパイル時にDWORD timeGetTime(void)関数が定義されていないことを示す.この関数を使用すると、クエリーによってタイミング制御が行われるので、タイミングイベントの制御を行うためにタイミングサイクルを確立する必要があります.サンプルエンジニアリングのTimer 5およびTimer 5_1. 方式6:マルチメディアタイマtimeSetEvent()関数を使用し、この関数のタイミング精度はms級である.この関数を使用すると、周期的な関数呼び出しを実現できます.サンプルエンジニアリングのTimer 6およびTimer 6_1.関数のプロトタイプは次のとおりです.
MMRESULT timeSetEvent( UINT uDelay, 
                               UINT uResolution, 
                               LPTIMECALLBACK lpTimeProc, 
                               WORD dwUser, 
                               UINT fuEvent )

この関数は、一時的なイベントまたは周期的なイベントとして使用できるタイミングコールバックイベントを設定します.イベントがアクティブになると、指定したコールバック関数が呼び出され、成功するとイベントの識別子コードが返されます.そうしないとNULLが返されます.関数のパラメータの説明は次のとおりです.
uDelay:          。
       Uresolution:          ,              。    1ms。
       LpTimeProc:        。
       DwUser:           。
       FuEvent:         :
       TIME_ONESHOT:uDelay          
       TIME_PERIODIC :  uDelay          。

具体的に適用する場合、timeSetEvent()関数を呼び出すことで、周期的に実行する必要があるタスクをLpTimeProcコールバック関数(タイミングサンプリング、制御など)に定義し、処理するイベントを完了することができます.タスク処理の時間は、サイクル間隔時間より大きくてはならないことに注意してください.また、タイマーの使用が完了したら、timeKillEvent()を呼び出して解放します.方式7:より精度が要求されるタイミング操作には,QueryPerformanceFrequency()とQueryPerformanceCounter()関数を用いるべきである.この2つの関数は、VCが提供するWindows 95以降のバージョンでのみ使用できる正確な時間関数であり、コンピュータにハードウェアから正確なタイマをサポートするように要求される.サンプルエンジニアリングのTimer 7、Timer 7_1、Timer7_2、Timer7_3. 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などの機器配置に関係する.(完)