実行時間を調べる処理 (2) C++で拡張を

前回、なぜ(1)と書いたかというとこういう思いがあったからなのですね。

C++のクラスを使って時間計測をもっと使いやすく、と言う物です。

オブジェクト指向であればたいていの言語で似たような書き方が出来るはずなので、科学技術計算で使いたい方は参考に。

インターフェイスクラス

まずはインターフェイスクラスです。一応Windows、Linux兼用を念頭に作ります。

クラス上ではタイムカウンタは64bit値として共通で扱えるようにし、32bit値でラウンドしてしまう物は出来る限り64bitで扱えるようにしてみます。

typedef unsigned long long time_value;
//タイムカウント
class ITimeCount{
public:
	virtual ~ITimeCount(){ } //デストラクタ
	virtual time_value GetTimeValue(void) = 0; //タイマ値を取得する
	virtual time_value GetSecondCount(void) = 0; //一秒間でカウントされるタイマ値を取得する
};
//32bit値を64bit値にするカウント拡張
class ITimeCountEx : public ITimeCount{
public:
	ITimeCountEx(){ m_tval.v = 0; } //コンストラクタ
	virtual ~ITimeCountEx(){ } //デストラクタ
	//タイマ値を取得する
	virtual time_value GetTimeValue(void)
	{
		unsigned int n = GetTimeValue32bit();
		if(m_tval.lt > n){ m_tval.ht++; }
		m_tval.lt = n;
		return m_tval.v;
	}
protected:
	//32bit=>64bit変換構造体(リトルエンディアン)
	typedef union _tval64{
		struct{
			unsigned int lt;
			unsigned int ht;
		};
		time_value v;
	} TVAL64;
	virtual unsigned int GetTimeValue32bit(void) = 0; //32bitタイマ値を取得する
private:
	TVAL64 m_tval;
};

ちなみに、この32bitタイム値=>64bitタイム値の変換処理はマルチスレッドには対応していません。

マルチスレッドでも大丈夫にするためにはカウントが繰り上がるタイミングで排他処理を行う必要があります。

また、この方法の場合は繰り上がり処理を行うためにはある程度の間隔でタイム値の取得を行う必要があります。

別の方法としては「秒単位のタイム値の取得+周期越えを確認して周期分を加算する」と言う方法がありますが、面倒ですので。

各方法を実装

//clock()を使ったカウント
class CTimeClock : public ITimeCountEx{
public:
	CTimeClock(){ } //コンストラクタ
	virtual ~CTimeClock(){ } //デストラクタ
	virtual time_value GetSecondCount(void){ return CLOCKS_PER_SEC; } //一秒間でカウントされるタイマ値を取得する
protected:
	virtual unsigned int GetTimeValue32bit(void){ return ::clock(); } //32bitタイマ値を取得する
};
//GetTickCount()を使ったカウント
class CTimeGTC : public ITimeCountEx{
public:
	CTimeGTC(){ } //コンストラクタ
	virtual ~CTimeGTC(){ } //デストラクタ
	virtual time_value GetSecondCount(void){ return 1000; } //一秒間でカウントされるタイマ値を取得する
protected:
	virtual unsigned int GetTimeValue32bit(void){ return ::GetTickCount(); } //32bitタイマ値を取得する
};
//timeGetTime()を使ったカウント
class CTimeTGT : public ITimeCountEx{
public:
	CTimeTGT(){ ::timeGetDevCaps(&m_tc,sizeof(m_tc)); ::timeBeginPeriod(m_tc.wPeriodMin); } //コンストラクタ
	virtual ~CTimeTGT(){ ::timeEndPeriod(m_tc.wPeriodMin); } //デストラクタ
	virtual time_value GetSecondCount(void){ return 1000; } //一秒間でカウントされるタイマ値を取得する
protected:
	virtual unsigned int GetTimeValue32bit(void){ return ::timeGetTime(); } //32bitタイマ値を取得する
private:
	TIMECAPS m_tc;
};
//QueryPerformanceCounterを使ったカウント
class CTimeQPC : public ITimeCount{
public:
	CTimeQPC(){ } //コンストラクタ
	virtual ~CTimeQPC(){ }; //デストラクタ
	virtual time_value GetTimeValue(void){ LARGE_INTEGER li; ::QueryPerformanceCounter(&li); return li.QuadPart; } //タイマ値を取得する
	virtual time_value GetSecondCount(void){ LARGE_INTEGER li; ::QueryPerformanceFrequency(&li); return li.QuadPart; } //一秒間でカウントされるタイマ値を取得する
};
//rdtscを使ったカウント
class CTimeRDTSC : public ITimeCount{
public:
	CTimeRDTSC() : m_cs(0){ GetClockSecond(); } //コンストラクタ
	virtual ~CTimeRDTSC(){ }; //デストラクタ
	virtual time_value GetTimeValue(void){ return __rdtsc(); } //タイマ値を取得する
	virtual time_value GetSecondCount(void){ return m_cs; } //一秒間でカウントされるタイマ値を取得する
protected:
	typedef DWORD_PTR (WINAPI *LPFNSETTHREADAFFINITYMASK)(HANDLE hThread,DWORD_PTR dwThreadAffinityMask);
	typedef BOOL (WINAPI *LPFNGETPROCESSAFFINITYMASK)(HANDLE hProcess,DWORD_PTR *lpProcessAffinityMask,DWORD_PTR *lpSystemAffinityMask);
	//クロック時間を取得する
	void GetClockSecond(void)
	{
		HANDLE hThisProcess,hThisThread; HMODULE hKernelDLL;
		LPFNSETTHREADAFFINITYMASK fnSetThreadAffinityMask; LPFNGETPROCESSAFFINITYMASK fnGetProcessAffinityMask;
		
		DWORD_PTR dwProcessAffinityMask,dwSystemAffinityMask,dwAffinityMask;
		DWORD dwProcessPriority; int nThreadPriority; unsigned __int64 ts,te,tn; int i;
		hKernelDLL = ::GetModuleHandle(TEXT("kernel32.dll"));
		hThisProcess = ::GetCurrentProcess(); hThisThread = ::GetCurrentThread();
		fnSetThreadAffinityMask = NULL; fnGetProcessAffinityMask = NULL;
		dwProcessAffinityMask = 1; dwSystemAffinityMask = 1;
		if(hKernelDLL != NULL){
			fnGetProcessAffinityMask = (LPFNGETPROCESSAFFINITYMASK)::GetProcAddress(hKernelDLL,"GetProcessAffinityMask");
			if(fnGetProcessAffinityMask != NULL) fnGetProcessAffinityMask(hThisProcess,&dwProcessAffinityMask,&dwSystemAffinityMask);
			if(dwProcessAffinityMask != 1){
				fnSetThreadAffinityMask = (LPFNSETTHREADAFFINITYMASK)::GetProcAddress(hKernelDLL,"SetThreadAffinityMask");
				if(fnSetThreadAffinityMask != NULL){
					for(i = 0,dwAffinityMask = dwProcessAffinityMask;i < (int)(sizeof(dwProcessAffinityMask) * 8);i++){
						if(dwProcessAffinityMask & (DWORD_PTR)(1 << i)){ dwAffinityMask = (DWORD_PTR)(1 << i); break; }
					}
					fnSetThreadAffinityMask(hThisThread,dwAffinityMask);
				}
			}
		}
		dwProcessPriority = ::GetPriorityClass(hThisProcess); if(dwProcessPriority == 0) dwProcessPriority = NORMAL_PRIORITY_CLASS;
		nThreadPriority = ::GetThreadPriority(hThisThread); if(nThreadPriority == THREAD_PRIORITY_ERROR_RETURN) nThreadPriority = THREAD_PRIORITY_NORMAL;
		::SetPriorityClass(hThisProcess,REALTIME_PRIORITY_CLASS); ::SetThreadPriority(hThisThread,THREAD_PRIORITY_TIME_CRITICAL);
		{
			CTimeTGT tv;
			ts = __rdtsc();
			tn = tv.GetTimeValue() + 1000;
			while(tn > tv.GetTimeValue());
			te = __rdtsc();
			m_cs = te - ts;
		}
		::SetThreadPriority(hThisThread,nThreadPriority); ::SetPriorityClass(hThisProcess,dwProcessPriority);
		if(fnSetThreadAffinityMask != NULL) fnSetThreadAffinityMask(hThisThread,dwProcessAffinityMask);
	}
private:
	time_value m_cs;
};

rdtsc系以外はものすごく単純な実装ですね。役割がわかりやすいですので。

rdtsc内のtimeGetTimeを使って時間を測定していた部分はクラスが前にあるので使わせてもらっています。

時間確認クラス

//時間差分をはかる
class CTimer{
public:
	CTimer(ITimeCount *tc) : m_tc(tc){ Reset(); } //コンストラクタ
	~CTimer(){ } //デストラクタ
	void Stamp(void){ m_tn = m_tc->GetTimeValue(); }
	void Reset(void){ m_tb = m_tn = m_tc->GetTimeValue(); }
	time_value GetCount(void){ return m_tn - m_tb; }
	double GetSecond(void){ return (double)GetCount() / (double)(m_tc->GetSecondCount()); }
private:
	time_value m_tb,m_tn;
	ITimeCount *m_tc;
};

確認に必要な「リセット」「スタンプ」「取得」を実装しています。

内部で値を保持しているので外から見たときにはストップウォッチのように簡単な構造になっていますね。

使ってみる

int main(void)
{
	CTimeGTC tb; CTimer timer(&tb);
	timer.Reset();
	f();
	timer.Stamp();
	printf("f() took %lld count [%4.2f sec].\n",timer.GetCount(),timer.GetSecond());
	getchar();
	return 0;
}

見やすくなりましたね~。やっぱりオブジェクト化すると見通しがとても良くなります。

例で使っているのはGetTickCount版ですが、それ以外でもオブジェクト(名)が変わるだけでそれ以外は全く同じです。

クラス化すれば使いやすくなるかな

といっても、あくまで科学技術計算の時間取得を考えていますので、ゲーム用のタイマとしては問題があったりします。

その辺は上のコードを参考に自分で使える物を持っておくといいと思いますよ。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL