GetTickCountとtimeGetTimeによる時間測定
Windowsでは、ミリ秒単位の時間を測る時にGetTickCount()とtimeGetTime()という2つの関数が良く利用されます。この2つの関数は、マルチメディア向けAPIに含まれるtimeGetTime()の方が高精度(最高1ms)でゲームやマルチメディア処理に適しており、GetTickCount()の方は精度が悪い、と言われていますが、実際にどの程度の精度で時間測定ができるのか、測ってみました。
精度の測定
今回はある一定の周期を設定し、その周期のループを1000回繰り返してループ終了までの経過時間を測る事で精度を調べる事にしました。例えば、周期を1msにするとループが終わるまでに1ms×1000=1sかかるはずですが、周期を測る関数の精度が悪ければもっとかかるでしょう。
下の例は、周期dwLapミリ秒でGetTickCountの精度を測る例です。精度が良ければほぼdwLap秒で終了し、精度が悪ければずれてきます。条件を統一するために経過時間の測定にはtimeGetTimeを使いました。
/* 開始時間を保存 */
dwStart=timeGetTime();
dwLap=GetTickCount();
while (dwCount++<1000) { /* 周期で1000回ループ */
/* 周期の時間が経過するまで待つ */
while (dwLap+dwInt>=GetTickCount());
dwLap=GetTickCount();
}
周期の設定
周期は、リストボックスで設定できるようにします。選択できる周期として、1~12msの項目を入れておき、測定する時にリストボックスで選択されている項目を取得するようにしましょう。リストボックスの作成は、CreateWindowsにLISTを指定してリストボックスを作成し、作成したリストボックスにLB_ADDSTRINGメッセージを送って項目を追加していくだけです。項目を作成したら、LB_SELECTSTRINGメッセージを送ってデフォルトの項目を選択しておく事も出来ます。
hwIntL=CreateWindow("LISTBOX","LIST", /* リストボックス */
WS_CHILD | WS_VISIBLE | WS_BORDER |WS_VSCROLL,
128,64,128,96,hwnd,(HMENU)2,hInst,NULL);
/* リストボックスに項目追加 */
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)" 1ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)" 2ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)" 3ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)" 4ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)" 5ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)" 6ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)" 7ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)" 8ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)" 9ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)"10ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)"11ms*1000");
SendMessage(hwIntL,LB_ADDSTRING,0,(LPARAM)"12ms*1000");
/* リストボックスの項目選択 */
SendMessage(hwIntL,LB_SELECTSTRING,0,(LPARAM)" 1ms");
選択されている項目を取得する時は、LB_GETCURSELメッセージを送ると現在選択されている項目のインデックスが返って来ます。
SendMessage(hwIntL,LB_GETCURSEL,0,0);
プログラムでは、GetTickCount()とtimeGetTime()それぞれで周期を測るボタンhwTickB, hwTimeBを作りました。このボタンをクリックすると、リストボックスの値を読み周期を設定してから1000回のループに入ります。
case WM_COMMAND:
switch (LOWORD(wParam)) {
case 0: /* Tickボタン */
/* 周期設定 */
dwInt=SendMessage(hwIntL,LB_GETCURSEL,0,0);
/* 開始時間を保存 */
dwStart=timeGetTime();
dwLap=GetTickCount();
while (dwCount++<1000) { /* 周期で1000回ループ */
/* 周期の時間が経過するまで待つ */
while (dwLap+dwInt>=GetTickCount());
/* 周期の時間が経過するまで待つ */
dwLap=GetTickCount();
}
/* 結果表示 */
wsprintf(lpszStr,"経過時間%dms",timeGetTime()-dwStart);
MessageBox(hwnd,lpszStr,"終了",MB_OK);
break;
case 1: /* Timeボタン */
dwInt=SendMessage(hwIntL,LB_GETCURSEL,0,0);
dwStart=timeGetTime();
dwLap=timeGetTime();
while (dwCount++<1000) {
while (dwLap+dwInt>=timeGetTime());
dwLap=timeGetTime();
}
wsprintf(lpszStr,"経過時間%dms",timeGetTime()-dwStart);
MessageBox(hwnd,lpszStr,"終了",MB_OK);
break;
}
break;
プログラム
「Tick」ボタンでGetTickCount()、「Time」ボタンでtimeGetTime()の周期を測ります。リストボックスで周期を指定してください。周期×1000回の時間を測定して表示します。
私の環境で測ると、timeGetTimeに関してはほぼ正確に周期×1000の時間で返って来ますが、GetTickCountだと1ms*1000を指定しても大体5秒ぐらいかかりますね。1~5msの時は約5秒、6~10msだと約10秒で返ってくるので、GetTickCount()は精度としては約5msという事になるのでしょう。ただ、1msの精度が出る事もあるので環境や状況によっても変わってくるようです。
プログラムをビルド゙する時には、プロジェクトの設定でwinmm.libをリンク指定に加えてください。なお、GetTickCount(), timeGetTime()はともにDWORDで起動後の経過時間を返すので、このプログラムを49日連続で動かしているマシンで実行するのは避けた方が良いでしょう(^^;。