Windowsでの処理時間取得[C言語] [開発環境]
Windowsでミリ秒単位で処理時間を計算するために使う関数に、GetTickCountとtimeGetTimeが良く使われる。timeGetTime関数は1msの精度を持っており、一般にGetTickCountより良いと言われている。さらにQueryPerformanceCounterとSleepを使った場合の分解能を比較した。
動作テストとしては、コンテック製デジタルIOボードの出力をON/OFFするアプリを作成し、結果のパルスをオシロスコープで確認した。
結果としては、高分解能パフォーマンスカウンタが存在する場合は、QueryPerformanceCounter、存在しない場合は、timeGetTimeが良さそうであった。ちなみに高分解能パフォーマンスカウンタの有無はQueryPerformanceCounter関数で確認できる。
GetTickCount
システムを起動した後の経過時間を、ミリ秒(ms)単位で取得します。この時間は、システムタイマの分解能による制限を受けます。
DWORD GetTickCount(VOID);
パラメータ
なし
戻り値
関数が成功すると、システムを起動した後の経過時間が、ミリ秒単位で返ります。
解説
経過時間は DWORD 型で保存されています。システムを 49.7 日間連続して動作させると、経過時間は 0 に戻ります。
より高い分解能のタイマが必要な場合は、「」(マルチメディアタイマ)または「」(高分解能タイマ)を使います。
テスト
DWORD st; // 開始時間
DWORD now; // 現在時間
DWORD pass=10; // 経過時間(msec)while (1) {
st = GetTickCount();while (1) {
now = GetTickCount();
if ((now - st) > pass) break;
}if (ledOn == true) {
Ret=DioOutBit(Id, 4, 0);
if (Ret!=0) {
printf("DioOutBit off NG[0x%x]\n",Ret);
}
ledOn=false;
}
else {
Ret=DioOutBit(Id, 4, 1);
if (Ret!=0) {
printf("DioOutBit on NG[0x%x]\n",Ret);
}
ledOn=true;
}
}
passパルス幅を10ms以下に設定しても、実動作は15ms以下にはできない。この関数はWindowsのシステムタイマーの設定以下にはできない。またGetTickCount()関数によるシステムタイマー分解能の変更にも対応していないことも再確認できた。
timeGetTime
関数を使用するためにはwinmm.libをリンクし mmsystem.hをインクルードする。 GetTickCountと同様にWindows起動からの経過時間をミリ秒単位で返す。 timeBeginPeriod,timeEndPeriodを使うことで精度の調整をすることができる。 デフォルトで5ミリ秒に設定されており、最高で1ミリ秒の精度指定が可能
DWORD timeGetTime(VOID);
パラメータ
なし
戻り値
関数が成功すると、システム時刻がミリ秒単位で返ります。
解説
timeGetTime 関数で返される値は DWORD 値であることに注意してください。戻り値は 0 ミリ秒から 2^32 ミリ秒の間を循環します。2^32 ミリ秒は約 49.71 日にあたります。計算に timeGetTime 関数の戻り値を直接使うようなコードでは、特にコードの実行を制御する場合などに、問題が発生する可能性があります。計算では常に、2 つの timeGetTime 関数の戻り値の差を使います
テスト
int i=0;
DWORD st; // 開始時間
DWORD now; // 現在時間
DWORD pass=1; // 経過時間(msec)// システムタイマーの分解能を1ミリ秒に設定
timeBeginPeriod(1);
while (1) {
st = timeGetTime();
while (1) {
now = timeGetTime();
if ((now - st) > pass) break;
}if (ledOn == true) {
Ret=DioOutBit(Id, 4, 0);
if (Ret!=0) {
printf("DioOutBit off NG[0x%x]\n",Ret);
}
ledOn=false;
}
else {
Ret=DioOutBit(Id, 4, 1);
if (Ret!=0) {
printf("DioOutBit on NG[0x%x]\n",Ret);
}
ledOn=true;
}i++;
if (i>50000) break;
}
// 分解能を戻す
timeEndPeriod(1);
GetTickCount()関数と同様に、単純にtimeGetTime()関数を呼ぶだけでは、システムタイマーの影響を受けて、15ms以下にはできなかった。しかしtimeBeginPeriod()/timeEndPeriod()関数を入れることで最小約2ms幅のパルスを出力する事が可能となることが確認できた。
QueryPerformanceCounter
高分解能パフォーマンスカウンタが存在する場合、そのカウンタの現在の値を取得します。
LARGE_INTEGER構造体に、高分解能パフォーマンスカウンタの現在値が格納される。 QueryPerformanceFrequencyを使うことでカウンタの周波数を知ることが できるので、カウンタの差分を周波数で割れば処理時間を算出できる。1ミリ秒よりも小さい 間隔で測定可能。
BOOL QueryPerformanceCounter(
LARGE_INTEGER *lpPerformanceCount // カウンタの値
);
パラメータ
1 個の変数へのポインタを指定します。関数から制御が返ると、この変数に、高分解能パフォーマンスカウンタの現在の値が格納されます。インストール先のハードウェアが高分解能パフォーマンスカウンタをサポートしていない場合、この変数に 0 が格納されることがあります。
戻り値
インストール先のハードウェアが高分解能パフォーマンスカウンタをサポートしている場合、0 以外の値が返ります。
関数が失敗すると、0 が返ります。拡張エラー情報を取得するには、 関数を使います。たとえば、インストール先のハードウェアが高分解能パフォーマンスカウンタをサポートしていない場合、この関数は失敗します
解説
マルチプロセッサのコンピュータを使っている場合、どのプロセッサを呼び出しても問題はありません。ただし、BIOS または HAL(ハードウェアエミュレーションレイヤ)のバグが原因で、異なったプロセッサを呼び出すと、異なった結果を取得する可能性があります。特定のスレッドに対するプロセッサの親和性を指定する(特定のスレッドでただ 1 つのプロセッサを使うよう指定する)には、 関数を使います
テスト
int i=0;
LARGE_INTEGER nFreq, nStart, nNow;
DWORD dwTime;
DWORD pass=1; // 経過時間(msec)//変数の初期化
memset(&nFreq, 0x00, sizeof nFreq);
memset(&nStart, 0x00, sizeof nStart);
memset(&nNow, 0x00, sizeof nNow);
dwTime = 0;QueryPerformanceFrequency(&nFreq);
while (1) {
QueryPerformanceCounter(&nStart);
while (1) {
QueryPerformanceCounter(&nNow);
dwTime = (DWORD)((nNow.QuadPart - nStart.QuadPart) * 1000 / nFreq.QuadPart);
if (dwTime >= pass) break;
}if (ledOn == true) {
Ret=DioOutBit(Id, 4, 0);
if (Ret!=0) {
printf("DioOutBit off NG[0x%x]\n",Ret);
}
ledOn=false;
}
else {
Ret=DioOutBit(Id, 4, 1);
if (Ret!=0) {
printf("DioOutBit on NG[0x%x]\n",Ret);
}
ledOn=true;
}i++;
if (i>50000) break;
}
QueryPerformanceCounter()関数を使用することでシステムタイマーの設定変更なしに1ms幅のパルスを出力する事ができた。
Sleep
参考にSleep()関数を使用したパターンもテストした。なおシステムタイマーの影響を受けることが明確だったので、最初からtimeBeginPeriod()/timeEndPeriod()関数を入れてテストした。
VOID Sleep(
DWORD dwMilliseconds // 中断の時間
);
パラメータ
実行を中断する時間を、ミリ秒(ms)単位で指定します。0 を指定すると、現在のスレッドは、優先順位が等しく実行の準備ができているほかのスレッドに残りのタイムスライスを譲ります。そのようなスレッドが存在しない場合は、この関数は即座に制御を返します。INFINITE を指定すると、実行が無制限に中断されます。
戻り値
なし
解説
指定された時間にわたって、現在のスレッドの実行を中断します。
テスト
long Ret=0;
bool ledOn= false;
DWORD pass=1; // 経過時間(msec)while (1) {
// 分解能を1ミリ秒に設定
timeBeginPeriod(1);
Sleep(pass);
// 戻し
timeEndPeriod(1);if (ledOn == true) {
Ret=DioOutBit(Id, 4, 0);
if (Ret!=0) {
printf("DioOutBit off NG[0x%x]\n",Ret);
}
ledOn=false;
}
else {
Ret=DioOutBit(Id, 4, 1);
if (Ret!=0) {
printf("DioOutBit on NG[0x%x]\n",Ret);
}
ledOn=true;
}
}
timeGetTime()関数とほぼ同じ、2ms幅のパルスを得ることが出来た。
コメント 0