Realization of High Precision Timer and Acquisition of Precision Time in Windows

Keywords: Windows SDK

Many core technologies in communication, VOIP, video and other fields require high time accuracy, such as data acquisition, time synchronization, media flow smoothing control, congestion algorithm, etc. Many technologies are calculated and controlled in milliseconds. But at the beginning of Windows design, it was not aimed at real-time system, so the time accuracy of Windows system was not high. The actual minimum unit was about 15 ms. As a result, all Windows time and thread-related operations could not be accurately controlled in 1ms.

 

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

 

 

  1. #pragma once  
  2.  
  3. #include <Windows.h>  
  4. #include <list>  
  5. #include <akumaslab/system/singleton.hpp>  
  6.   
  7. namespace akumaslab{  
  8.     namespace time{  
  9.         using std::list;  
  10.   
  11.         class PreciseTimerProvider  
  12.         {  
  13.             struct WaitedHandle{  
  14.                 HANDLE threadHandle;  
  15.                 LONGLONG elapsed;//timeout  
  16.             } ;  
  17.             typedef list< WaitedHandle > handle_list_type;  
  18.             typedef akumaslab::system::Singleton< PreciseTimerProvider > timer_type;  
  19.         public:  
  20.             PreciseTimerProvider(void):highResolutionAvailable(false), timerID(0)  
  21.             {  
  22.                 InitializeCriticalSection(&critical);  
  23.                 static LARGE_INTEGER systemFrequency;  
  24.                 if(0 != QueryPerformanceFrequency(&systemFrequency))  
  25.                 {  
  26.                     timeBeginPeriod(callbackInterval);  
  27.                     highResolutionAvailable = true;  
  28.                     countPerMilliSecond = systemFrequency.QuadPart/1000;  
  29.                     timerID = timeSetEvent(callbackInterval, 0, &PreciseTimerProvider::TimerFunc, NULL, TIME_PERIODIC);  
  30.                 }  
  31.             }  
  32.             //Suspend the current thread  
  33.             //@ milliSecond: timeout time, in milliseconds  
  34.             bool suspendCurrentThread(int milliSecond)  
  35.             {  
  36.                 if(milliSecond <= 0)return false;  
  37.                 if (!highResolutionAvailable)return false;  
  38.                 HANDLE currentThreadHandle = GetCurrentThread();  
  39.                 HANDLE currentProcessHandle = GetCurrentProcess();  
  40.                 HANDLE realThreadHandle(0);  
  41.                 DuplicateHandle(currentProcessHandle, currentThreadHandle, currentProcessHandle, &realThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);  
  42.                 WaitedHandle item;  
  43.                 item.threadHandle = realThreadHandle;  
  44.                 LARGE_INTEGER now;  
  45.                 QueryPerformanceCounter(&now);  
  46.                 now.QuadPart += milliSecond * countPerMilliSecond;  
  47.                 item.elapsed = now.QuadPart;  
  48.                 EnterCriticalSection(&critical);  
  49.                 waitList.push_back(item);  
  50.                 LeaveCriticalSection(&critical);  
  51.                 //Suspend threads  
  52.                 SuspendThread(realThreadHandle);  
  53.                 CloseHandle(realThreadHandle);  
  54.                 return true;  
  55.             }  
  56.             //Restore timeout threads  
  57.             void resumeTimeoutThread()  
  58.             {  
  59.                 if (!highResolutionAvailable)return;  
  60.                 LARGE_INTEGER now;  
  61.                 QueryPerformanceCounter(&now);  
  62.                 EnterCriticalSection(&critical);  
  63.                 for (handle_list_type::iterator ir = waitList.begin(); ir != waitList.end(); )  
  64.                 {  
  65.                     WaitedHandle& waited = *ir;  
  66.                     if (now.QuadPart >= waited.elapsed)  
  67.                     {  
  68.                         ResumeThread(waited.threadHandle);  
  69.                         ir = waitList.erase(ir);  
  70.                         continue;  
  71.                     }  
  72.                     ir++;  
  73.                 }                                 
  74.                 LeaveCriticalSection(&critical);  
  75.             }  
  76.             ~PreciseTimerProvider(){  
  77.                 if (0 != timerID)  
  78.                 {  
  79.                     timeKillEvent(timerID);  
  80.                     timerID = 0;  
  81.                     timeEndPeriod(callbackInterval);  
  82.                 }  
  83.                 DeleteCriticalSection(&critical);  
  84.             }  
  85.         private:  
  86.   
  87.             static void CALLBACK TimerFunc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)  
  88.             {  
  89.                 static bool initialed = false;  
  90.                 if (!initialed)  
  91.                 {  
  92.                     if (initialWorkThread())  
  93.                     {  
  94.                         initialed = true;  
  95.                     }  
  96.                     else{  
  97.                         return;  
  98.                     }  
  99.                 }  
  100.                 timer_type::getRef().resumeTimeoutThread();  
  101.             }  
  102.             //Adjusting the priority of timer worker threads  
  103.             static bool initialWorkThread()  
  104.             {  
  105.                 HANDLE realProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, _getpid());  
  106.                 if (NULL == realProcessHandle)  
  107.                 {  
  108.                     return false;  
  109.                 }  
  110.                 if (0 == SetPriorityClass(realProcessHandle, REALTIME_PRIORITY_CLASS))  
  111.                 {  
  112.                     CloseHandle(realProcessHandle);  
  113.                     return false;  
  114.                 }  
  115.                 HANDLE currentThreadHandle = GetCurrentThread();  
  116.                 HANDLE currentProcessHandle = GetCurrentProcess();  
  117.                 HANDLE realThreadHandle(0);  
  118.                 DuplicateHandle(currentProcessHandle, currentThreadHandle, currentProcessHandle, &realThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);  
  119.                 SetThreadPriority(realThreadHandle, THREAD_PRIORITY_TIME_CRITICAL);  
  120.                 //The replication handle must be closed  
  121.                 CloseHandle(realThreadHandle);  
  122.                 CloseHandle(realProcessHandle);  
  123.                 return true;  
  124.             }  
  125.         private:  
  126.             const static int callbackInterval = 1;  
  127.             CRITICAL_SECTION critical;  
  128.             MMRESULT timerID;  
  129.             LONGLONG countPerMilliSecond;  
  130.             bool highResolutionAvailable;  
  131.             handle_list_type waitList;  
  132.         };  
  133.         class PreciseTimer  
  134.         {  
  135.             typedef akumaslab::system::Singleton< PreciseTimerProvider > timer_type;  
  136.         public:  
  137.             static bool wait(int milliSecond)  
  138.             {  
  139.                 //static PreciseTimerProvider timer;  
  140.                 return timer_type::getRef().suspendCurrentThread(milliSecond);  
  141.             }  
  142.         };  
  143.     }  
  144. }  
 

DEMO

  1. int interval = 1;  
  2. int repeatCount = 50;  
  3. cout << getCurrentTime() << "test begin" << endl;  
  4. unit.reset();  
  5. for (int i = 0; i < repeatCount; i++)  
  6. {  
  7.     akumaslab::time::PreciseTimer::wait(interval);  
  8.     cout << getCurrentTime() << "/" << getNewTime() << " used " << unit.getPreciseElapsedTime() << " ms" << endl;  
  9.     unit.reset();  
  10. }  

 

2. Accurate time acquisition

  1. #pragma once  
  2.   
  3. #include <sys/timeb.h>  
  4. #include <time.h>  
  5. #include <Windows.h>  
  6. #include <akumaslab/system/singleton.hpp>  
  7.   
  8. namespace akumaslab{  
  9.     namespace time{  
  10.         struct HighResolutionTime  
  11.         {  
  12.             int year;  
  13.             int month;  
  14.             int day;  
  15.             int hour;  
  16.             int min;  
  17.             int second;  
  18.             int millisecond;  
  19.         };  
  20.         class CurrentTimeProvider  
  21.         {  
  22.         public:  
  23.             CurrentTimeProvider():highResolutionAvailable(false), countPerMilliSecond(0), beginCount(0)  
  24.             {  
  25.                 static LARGE_INTEGER systemFrequency;  
  26.                 if(0 != QueryPerformanceFrequency(&systemFrequency))  
  27.                 {  
  28.                     highResolutionAvailable = true;  
  29.                     countPerMilliSecond = systemFrequency.QuadPart/1000;  
  30.                     _timeb tb;  
  31.                     _ftime_s(&tb);  
  32.                     unsigned short currentMilli = tb.millitm;  
  33.                     LARGE_INTEGER now;  
  34.                     QueryPerformanceCounter(&now);  
  35.                     beginCount = now.QuadPart - (currentMilli*countPerMilliSecond);  
  36.                 }  
  37.             };  
  38.             bool getCurrentTime(HighResolutionTime& _time)  
  39.             {  
  40.                 time_t tt;  
  41.                 ::time(&tt);  
  42.                 tm now;  
  43.                 localtime_s(&now, &tt);  
  44.                 _time.year = now.tm_year + 1900;  
  45.                 _time.month = now.tm_mon + 1;  
  46.                 _time.day = now.tm_mday + 1;  
  47.                 _time.hour = now.tm_hour;  
  48.                 _time.min = now.tm_min;  
  49.                 _time.second = now.tm_sec;  
  50.                 if (!highResolutionAvailable)  
  51.                 {  
  52.                     _time.millisecond = 0;  
  53.                 }  
  54.                 else{  
  55.                     LARGE_INTEGER qfc;  
  56.                     QueryPerformanceCounter(&qfc);  
  57.                     _time.millisecond = (int)((qfc.QuadPart - beginCount)/countPerMilliSecond)%1000;  
  58.                 }  
  59.                 return true;  
  60.             }  
  61.         private:  
  62.             bool highResolutionAvailable;  
  63.             LONGLONG countPerMilliSecond;  
  64.             LONGLONG beginCount;  
  65.         };  
  66.         class CurrentTime  
  67.         {  
  68.         public:  
  69.             static bool get(HighResolutionTime& _time)  
  70.             {  
  71.                 return akumaslab::system::Singleton< CurrentTimeProvider >::getRef().getCurrentTime(_time);  
  72.             }  
  73.         };  
  74.     }  
  75. }  

DEMO:

  1. HighResolutionTime time;  
  2. CurrentTime::get(time);  
  3. const int size = 20;  
  4. char buf[size] = {0};  
  5. _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.

top 1 

Posted by newbie8899 on Sun, 23 Jun 2019 15:08:55 -0700