Affected operations include Sleep, GetTickCount, _ftime, and so on. For example, you call Sleep(2) and expect the thread to wake up automatically after 2ms, but the actual result may be 15ms or even 2xms, which has little impact on simple applications, but for systems with very high precision requirements, such a problem is very fatal.
The code idea is as follows:
1. High precision timer. The thread requesting Sleep is suspended and managed in Singleton mode. The periodic callback function of Windows MultiMedia SDK is used in the background to continuously detect and reply to the threads when they arrive. Query Performance Counter/Query Performance Frequency is used to timing the timeout time and the current time to ensure the reliability of the overall function.
2. Accurate time acquisition. Since both _ftime and GetTickCount, which can be acquired in milliseconds, are affected by the time accuracy of Windows systems, the minimum unit is only 15 ms, so it is necessary to use Query Performance Counter/Query Performance Frequency for accurate timing. The code first gets the exact scale of the starting time based on _ftime, and then calculates the current exact time based on the difference.
The Singleton pattern in the code can find many implementations, so I won't go into details in this article.
Code (VS2005 c++ compiled)
1. High Precision Timer
- #pragma once
- #include <Windows.h>
- #include <list>
- #include <akumaslab/system/singleton.hpp>
- namespace akumaslab{
- namespace time{
- using std::list;
- class PreciseTimerProvider
- {
- struct WaitedHandle{
- HANDLE threadHandle;
- LONGLONG elapsed;//timeout
- } ;
- typedef list< WaitedHandle > handle_list_type;
- typedef akumaslab::system::Singleton< PreciseTimerProvider > timer_type;
- public:
- PreciseTimerProvider(void):highResolutionAvailable(false), timerID(0)
- {
- InitializeCriticalSection(&critical);
- static LARGE_INTEGER systemFrequency;
- if(0 != QueryPerformanceFrequency(&systemFrequency))
- {
- timeBeginPeriod(callbackInterval);
- highResolutionAvailable = true;
- countPerMilliSecond = systemFrequency.QuadPart/1000;
- timerID = timeSetEvent(callbackInterval, 0, &PreciseTimerProvider::TimerFunc, NULL, TIME_PERIODIC);
- }
- }
- //Suspend the current thread
- //@ milliSecond: timeout time, in milliseconds
- bool suspendCurrentThread(int milliSecond)
- {
- if(milliSecond <= 0)return false;
- if (!highResolutionAvailable)return false;
- HANDLE currentThreadHandle = GetCurrentThread();
- HANDLE currentProcessHandle = GetCurrentProcess();
- HANDLE realThreadHandle(0);
- DuplicateHandle(currentProcessHandle, currentThreadHandle, currentProcessHandle, &realThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
- WaitedHandle item;
- item.threadHandle = realThreadHandle;
- LARGE_INTEGER now;
- QueryPerformanceCounter(&now);
- now.QuadPart += milliSecond * countPerMilliSecond;
- item.elapsed = now.QuadPart;
- EnterCriticalSection(&critical);
- waitList.push_back(item);
- LeaveCriticalSection(&critical);
- //Suspend threads
- SuspendThread(realThreadHandle);
- CloseHandle(realThreadHandle);
- return true;
- }
- //Restore timeout threads
- void resumeTimeoutThread()
- {
- if (!highResolutionAvailable)return;
- LARGE_INTEGER now;
- QueryPerformanceCounter(&now);
- EnterCriticalSection(&critical);
- for (handle_list_type::iterator ir = waitList.begin(); ir != waitList.end(); )
- {
- WaitedHandle& waited = *ir;
- if (now.QuadPart >= waited.elapsed)
- {
- ResumeThread(waited.threadHandle);
- ir = waitList.erase(ir);
- continue;
- }
- ir++;
- }
- LeaveCriticalSection(&critical);
- }
- ~PreciseTimerProvider(){
- if (0 != timerID)
- {
- timeKillEvent(timerID);
- timerID = 0;
- timeEndPeriod(callbackInterval);
- }
- DeleteCriticalSection(&critical);
- }
- private:
- static void CALLBACK TimerFunc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
- {
- static bool initialed = false;
- if (!initialed)
- {
- if (initialWorkThread())
- {
- initialed = true;
- }
- else{
- return;
- }
- }
- timer_type::getRef().resumeTimeoutThread();
- }
- //Adjusting the priority of timer worker threads
- static bool initialWorkThread()
- {
- HANDLE realProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, _getpid());
- if (NULL == realProcessHandle)
- {
- return false;
- }
- if (0 == SetPriorityClass(realProcessHandle, REALTIME_PRIORITY_CLASS))
- {
- CloseHandle(realProcessHandle);
- return false;
- }
- HANDLE currentThreadHandle = GetCurrentThread();
- HANDLE currentProcessHandle = GetCurrentProcess();
- HANDLE realThreadHandle(0);
- DuplicateHandle(currentProcessHandle, currentThreadHandle, currentProcessHandle, &realThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
- SetThreadPriority(realThreadHandle, THREAD_PRIORITY_TIME_CRITICAL);
- //The replication handle must be closed
- CloseHandle(realThreadHandle);
- CloseHandle(realProcessHandle);
- return true;
- }
- private:
- const static int callbackInterval = 1;
- CRITICAL_SECTION critical;
- MMRESULT timerID;
- LONGLONG countPerMilliSecond;
- bool highResolutionAvailable;
- handle_list_type waitList;
- };
- class PreciseTimer
- {
- typedef akumaslab::system::Singleton< PreciseTimerProvider > timer_type;
- public:
- static bool wait(int milliSecond)
- {
- //static PreciseTimerProvider timer;
- return timer_type::getRef().suspendCurrentThread(milliSecond);
- }
- };
- }
- }
DEMO
- int interval = 1;
- int repeatCount = 50;
- cout << getCurrentTime() << "test begin" << endl;
- unit.reset();
- for (int i = 0; i < repeatCount; i++)
- {
- akumaslab::time::PreciseTimer::wait(interval);
- cout << getCurrentTime() << "/" << getNewTime() << " used " << unit.getPreciseElapsedTime() << " ms" << endl;
- unit.reset();
- }
2. Accurate time acquisition
- #pragma once
- #include <sys/timeb.h>
- #include <time.h>
- #include <Windows.h>
- #include <akumaslab/system/singleton.hpp>
- namespace akumaslab{
- namespace time{
- struct HighResolutionTime
- {
- int year;
- int month;
- int day;
- int hour;
- int min;
- int second;
- int millisecond;
- };
- class CurrentTimeProvider
- {
- public:
- CurrentTimeProvider():highResolutionAvailable(false), countPerMilliSecond(0), beginCount(0)
- {
- static LARGE_INTEGER systemFrequency;
- if(0 != QueryPerformanceFrequency(&systemFrequency))
- {
- highResolutionAvailable = true;
- countPerMilliSecond = systemFrequency.QuadPart/1000;
- _timeb tb;
- _ftime_s(&tb);
- unsigned short currentMilli = tb.millitm;
- LARGE_INTEGER now;
- QueryPerformanceCounter(&now);
- beginCount = now.QuadPart - (currentMilli*countPerMilliSecond);
- }
- };
- bool getCurrentTime(HighResolutionTime& _time)
- {
- time_t tt;
- ::time(&tt);
- tm now;
- localtime_s(&now, &tt);
- _time.year = now.tm_year + 1900;
- _time.month = now.tm_mon + 1;
- _time.day = now.tm_mday + 1;
- _time.hour = now.tm_hour;
- _time.min = now.tm_min;
- _time.second = now.tm_sec;
- if (!highResolutionAvailable)
- {
- _time.millisecond = 0;
- }
- else{
- LARGE_INTEGER qfc;
- QueryPerformanceCounter(&qfc);
- _time.millisecond = (int)((qfc.QuadPart - beginCount)/countPerMilliSecond)%1000;
- }
- return true;
- }
- private:
- bool highResolutionAvailable;
- LONGLONG countPerMilliSecond;
- LONGLONG beginCount;
- };
- class CurrentTime
- {
- public:
- static bool get(HighResolutionTime& _time)
- {
- return akumaslab::system::Singleton< CurrentTimeProvider >::getRef().getCurrentTime(_time);
- }
- };
- }
- }
DEMO:
- HighResolutionTime time;
- CurrentTime::get(time);
- const int size = 20;
- char buf[size] = {0};
- _snprintf_s(buf, size, size, "%02d:%02d %02d:%02d:%02d.%03d ", time.month, time.day, time.hour, time.min, time.second, time.millisecond);
The test results are as follows. The following figure is the result of Sleep of high precision timer according to 1ms. The left side is timed with _ftime and the right side is timed with precise time. Generally speaking, although it can not reach 100% reliability, it has been greatly improved compared with the original 15ms. It is expected that Windows can provide real high-precision time management technology as soon as possible.