PerformanceCounterを使ったマイクロ秒時計
プログラムメモ | 2010/09/18 Sat 13:05
| PerformanceCounterを使ったマイクロ秒時計
Linuxには、gettimeofdayがありますが、
Windowsには、マイクロ秒まで計測可能なAPIは用意されていません。
PerformanceCounterを使えばかなり正確な時間間隔を知ることはできますが、
時計として利用することは簡単ではありません。
ある程度観測をしていると、ansi標準の_ftimeとはズレていきます。
このため、_ftimeをベースとしてミリ秒以下の部分をPerformanceCounterから得ることを考えましたが、秒の変わり目で時計が逆に進む場合があります。
そこで、ftimeとPerformanceCounterから得られる時間差を調節し、
時計が逆に進むことがないようなマイクロ秒時計を作ってみました。
マイクロ秒を得る128bit除算を行っています。
ある時間差(t0〜t1)があったとして
t0,t1はQueryPerformanceCounterによって得られる値とします。
t = t1 - t0、この時間間隔は、周波数:f(QueryPerformanceFrequencyによって得られる)として
t / f で得られますが、
整数演算でマイクロ秒を得たいので
t * 1000000 / f
となります。
このままdoubleで演算しても良いのですが整数演算に拘って、
t * (1024*1024 - 48576) / f
= 1024*1024 * t / f * (1 - 1 / 21.58629776021)
= 1024*1024 * t / f * (1 - 1024*1024 / 22634874)
A = (t << 20) / f
と置けば
≒A - (A << 20) / 22634874 [μSec]となります。
これは、64bitを超える演算になりますのでforで回して計算しています。
テスト用コードでは、usecクラスから得られたマイクロ秒時間と共に、
_ftimeによる「秒、ミリ秒」も表示しています。
usec::getでは、ftimeとPerformanceCounterから得られる時間差を調節しているので、
それほど精度の高い時計ではありませんが、ミリ秒単位の時計だとループ間隔を短くSleep(10)とかにすると、同じ時間が続いてしまいます。
この時計の場合は、ループ間隔(Sleep)を短くしても、同じ時間が並ぶようなことはありません。
※高分解能パフォーマンスカウンタがないPCでは、ミリ秒単位の出力となります。
ソースはこちら
Tags: プログラムメモ
Linuxには、gettimeofdayがありますが、
Windowsには、マイクロ秒まで計測可能なAPIは用意されていません。
PerformanceCounterを使えばかなり正確な時間間隔を知ることはできますが、
時計として利用することは簡単ではありません。
ある程度観測をしていると、ansi標準の_ftimeとはズレていきます。
このため、_ftimeをベースとしてミリ秒以下の部分をPerformanceCounterから得ることを考えましたが、秒の変わり目で時計が逆に進む場合があります。
そこで、ftimeとPerformanceCounterから得られる時間差を調節し、
時計が逆に進むことがないようなマイクロ秒時計を作ってみました。
usec.h // (c) Hundredsoft Corporation 2010 All rights reserved. #ifndef USEC_INCLUDED #define USEC_INCLUDED class usec { public: usec(); ~usec(){} __int64 get(); private: __int64 microfunc(__int64 a, __int64 b); __int64 usec::getstm(); LARGE_INTEGER m_ti; LARGE_INTEGER m_tfi; __int64 m_msi; __int64 m_lastus; bool m_isSuported; }; #endif
usec.cpp // (c) Hundredsoft Corporation 2010 All rights reserved. #include #include #include #include "usec.h" usec::usec() { m_isSuported = false; if (QueryPerformanceFrequency(&m_tfi)){ if (QueryPerformanceCounter(&m_ti)){ m_isSuported = true; } } m_msi = getstm(); } __int64 usec::microfunc(__int64 a, __int64 b) { __int64 ah, al, a0, m = 0; int i; ah = (a >> 44) & 0xffff; al = a << 20; for (i=0; i<128; i++){ m <<= 1; if (ah < 0) m |= 1; ah <<= 1; if (al < 0) ah |= 1; al <<= 1; if (m >= b){ m -= b; al |= 1; } } a0 = al; ah = (a0 >> 44) & 0xffff; al = a0 << 20; m = 0; for (i=0; i<128; i++){ m <<= 1; if (ah < 0) m |= 1; ah <<= 1; if (al < 0) ah |= 1; al <<= 1; if (m >= 22634874){ m -= 22634874; al |= 1; } } return a0 - al; } __int64 usec::getstm() { _timeb tb; _ftime(&tb); struct tm* stm = localtime(&tb.time); __int64 stms = (stm->tm_hour * 3600000 + stm->tm_min * 60000 + stm->tm_sec * 1000 + tb.millitm); return stms * 1000; } __int64 usec::get() { __int64 utm; if (m_isSuported){ LARGE_INTEGER t, tf; QueryPerformanceFrequency(&tf); QueryPerformanceCounter(&t); __int64 stms = getstm(); __int64 diff = t.QuadPart-m_ti.QuadPart; if (diff > 0){ utm = m_msi + microfunc(diff, tf.QuadPart); diff = utm - stms; }else{ utm = stms; diff = 999999999; } __int64 absdiff = (diff < 0) ? -diff : diff; if (m_tfi.QuadPart != tf.QuadPart || absdiff > 10000){ m_tfi = tf; m_ti = t; m_msi = stms; if (diff > 0){ if (utm - m_lastus > 10 ) utm = (m_lastus + utm) / 2; else utm = m_lastus + 2; }else{ if (absdiff > 3600000000) utm = m_msi; else utm = (utm + m_msi) / 2; } } m_lastus = utm; }else{ utm = getstm(); } return utm; }
usec::microfuncは、PerformanceCounterから得られる値(LARGE_INTEGER)から
テスト用コード main.cpp #include #include #include #include "usec.h" #include #include int main(int argc, char**argv) { usec ut; while(1){ char work[256]; __int64 utm = ut.get(); _timeb tb; _ftime(&tb); struct tm* stm = localtime(&tb.time); sprintf(work, "%02d:%02d:%02d.%06d [%02d.%03d]", (int)((utm / 3600000000) % 24), (int)((utm / 60000000) % 60), (int)((utm / 1000000) % 60), (int)(utm % 1000000), stm->tm_sec, tb.millitm ); printf("%s\n", work); Sleep(300); } }
マイクロ秒を得る128bit除算を行っています。
ある時間差(t0〜t1)があったとして
t0,t1はQueryPerformanceCounterによって得られる値とします。
t = t1 - t0、この時間間隔は、周波数:f(QueryPerformanceFrequencyによって得られる)として
t / f で得られますが、
整数演算でマイクロ秒を得たいので
t * 1000000 / f
となります。
このままdoubleで演算しても良いのですが整数演算に拘って、
t * (1024*1024 - 48576) / f
= 1024*1024 * t / f * (1 - 1 / 21.58629776021)
= 1024*1024 * t / f * (1 - 1024*1024 / 22634874)
A = (t << 20) / f
と置けば
≒A - (A << 20) / 22634874 [μSec]となります。
これは、64bitを超える演算になりますのでforで回して計算しています。
テスト用コードでは、usecクラスから得られたマイクロ秒時間と共に、
_ftimeによる「秒、ミリ秒」も表示しています。
usec::getでは、ftimeとPerformanceCounterから得られる時間差を調節しているので、
それほど精度の高い時計ではありませんが、ミリ秒単位の時計だとループ間隔を短くSleep(10)とかにすると、同じ時間が続いてしまいます。
この時計の場合は、ループ間隔(Sleep)を短くしても、同じ時間が並ぶようなことはありません。
※高分解能パフォーマンスカウンタがないPCでは、ミリ秒単位の出力となります。
ソースはこちら
Tags: プログラムメモ
author : HUNDREDSOFT | - | -