Windows環境における高精度時刻取得について

結論

Windows8未満

 GetSystemTimeAsFileTime

Windows8以降

 以下を比較検討のうえ、採用。
 GetSystemTimePreciseAsFileTime
 GetSystemTimeAsFileTime 


上記に関するMicrosoftのまとめ
docs.microsoft.com


以下は検証の記録。

事前確認

事前知識

分解能

表現粒度を指す    
例:12:11:59.999→分解能1ミリ、12:11:59→分解能1秒

精度

判別可能な最小の断面
例:0.111,0.112,0.112 ... →精度1ミリ、0.110, 0.125,0.140... 精度15ミリ

検証環境

OS:Windows10 Pro

f:id:metatrading:20170617223904p:plain

分解能の確認

分解能の確認コード
// SystimeSample.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
int main()
{
    DWORD dwTimeAdjustment;
    DWORD dwTimeIncrement;
    BOOL bTimeAdjustmentDisabled;

    GetSystemTimeAdjustment(&dwTimeAdjustment, &dwTimeIncrement, &bTimeAdjustmentDisabled);
    printf("分解能(クロック割り込み間隔):%f ms\n", (double)dwTimeIncrement / 10000.0);
    printf("調整機能:%s\n", (bTimeAdjustmentDisabled ? "True" : "False"));
    printf("加算値(単位:ナノ秒):%f nano \n", GetSystemTimeAdjustment);

    TIMECAPS ptc;
    timeGetDevCaps(&ptc, sizeof(ptc));
    printf("マルチメディアタイマー分解能:%d ms\n", ptc.wPeriodMin);

    LARGE_INTEGER lpFrequency;
    QueryPerformanceFrequency(&lpFrequency);
    printf("高分解能カウンター分解能:%f ms\n", 1000.0 / lpFrequency.QuadPart);
    return 0;
}
結果

分解能(クロック割り込み間隔):15.625000 ms
調整機能:True
加算値(単位:ナノ秒):0.000000 nano
マルチメディアタイマー分解能:1 ms
高分解能カウンター分解能:0.000428 ms


上記はあくまでRTC(リアルタイムクロック・・・ハードウェア クロック)についての情報ぽい。

検証方法

APIを10万回連続で実行し、ミリ秒部分の更新が起きた際に、前回との差を記録する。

Windows previous versions documentation | Microsoft Docsによる計測結果

差分(ms) 出現回数
1 99995
2 1
12 1
15 1
32 2

Windows previous versions documentation | Microsoft Docsによる計測結果

差分(ms) 出現回数
1 99993
2 2
18 1
22 1
29 1
30 1
31 1

Windows previous versions documentation | Microsoft Docsによる計測結果

差分(ms) 出現回数
1 99997
2 2
32 1

GetSystemTimePreciseAsFileTime関数による計測結果

さらに調べると・・・Win8以降、高精度のシステム時刻取得API「GetSystemTimePreciseAsFileTime」が追加されている。

差分(ms) 出現回数
1 99995
4 1
3 1
6 1
2 1
31 1

環境によっては、GetSystemTimePreciseAsFileTimeが遅い場合もあるらしい。

GetSystemTimePreciseAsFileTimeが1ミリ秒の精度を持つのはドキュメントを読むと納得出来るが、
他の関数も1ミリ秒の精度が出ているのはなぜだろうか。


10万回の検証コード

// SystimeSample.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <unordered_map>
#include <iostream>
const int N = 100001;
const int ignoreN = 0;
int main()
{
	int secondAndMillseconds[N][2];
	std::fill(secondAndMillseconds[0], secondAndMillseconds[N], 0);

	SYSTEMTIME tm, oldTime;
	FILETIME ft;
	GetSystemTimePreciseAsFileTime(&ft);
	//GetSystemTimeAsFileTime(&ft);
	//GetSystemTime(&tm);
	//GetLocalTime(&tm);
	FileTimeToSystemTime(&ft, &tm);

	oldTime = tm;

	for (int i = 0; i < N;) {
		GetSystemTimePreciseAsFileTime(&ft);
		//GetSystemTimeAsFileTime(&ft);
		FileTimeToSystemTime(&ft, &tm);
		//GetSystemTime(&tm);
		//GetLocalTime(&tm);

		if (tm.wMilliseconds != oldTime.wMilliseconds) {
		//	printf("%d:%d:%d.%d\n", tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
			secondAndMillseconds[i][0] = tm.wSecond;
			secondAndMillseconds[i][1] = tm.wMilliseconds;

			oldTime = tm;
			i++;
		}
	}
	int oldSecond = 0;
	int oldMillSecond = 0;
	int second = 0;
	int millSecond = 0;
	int diffMillSecond = 0;

	// 差分別の出現回数map
	std::unordered_map<int, int> map;

	int startIndex = ignoreN;
	oldSecond = secondAndMillseconds[startIndex][0];
	oldMillSecond = secondAndMillseconds[startIndex][1];
	for (int i = startIndex + 1; i < N; i++) {
		// 0秒と59秒を比べる状況の考慮
		if (secondAndMillseconds[i][0] == 0 && oldSecond == 59) {
			second = 60;
		}
		else {
			second = secondAndMillseconds[i][0];
		}
		millSecond = secondAndMillseconds[i][1];
		diffMillSecond = (second * 1000 + millSecond) - (oldSecond * 1000 + oldMillSecond);
		if (map[diffMillSecond] == 0) {
			map[diffMillSecond] = 1;
		}
		else {
			map[diffMillSecond] = map[diffMillSecond] + 1;
		}
		oldSecond = secondAndMillseconds[i][0];
		oldMillSecond = secondAndMillseconds[i][1];
	}
	for (auto itr = map.begin(); itr != map.end(); ++itr) {
		std::cout << itr->first           // キーを表示
			<< "," << itr->second << "\n";    // 値を表示
	}

	return 0;
}