Timers: A continuous update for Windows, a high-precision time provider

Source: Internet
Author: User
Tags filetime



Original:Johan Nilsson

Translation:Lxhui



Source:MSDN Magazine March 2004 (Timers ...)

Original code Download: HighResolutionTimer.exe (404KB)

This article assumes that you are familiar with the C + + and Win32 APIs

Overview
The timestamp obtained from Windows NT (Timestamp), based on the hardware you are using, has a maximum accuracy of 10 to 15 milliseconds. However, there are times when you need time to label frequent events to get higher accuracy and be more satisfying. For example, what if you are dealing with a thread, or if you are implementing some other task at a frequency of no less than 10 milliseconds? For better accuracy, the recommended approach involves using performance counters and system time to calculate smaller time increments. However, the use of performance counter technology has its own problems. This article will reveal a feasible way to overcome the inherent limitations of the method.


Why are you interested in getting system time that is less than 1 milliseconds accurate? While I was working, I found it necessary to determine the sequence of events that were raised by different threads in my process. These events also need to be associated with absolute time, but notice that the actual accuracy of the system time is no more than 10 milliseconds in granularity.
In the subsequent content of this article, I will explain the limitations of the system's time accuracy, the steps to be resolved, and some general flaws. The implementation of the example program can be downloaded from the link at the beginning of this article. The source code for these files is in Visual C + +? 7.1 and Windows? XP Professional Edition is written under test. As I write this article, I frequently refer to the Windows NT® operating system family (Windows NT 4.0, Windows 2000, or Windows XP) products, not a specific version. The Win32 used in this article? For the parameter types and usage of APIs, see the MSDN library/platform SDK documentation.

who actually has this kind of demand?
I recently searched the Internet with "Windows NT millisecond time resolution" as a keyword, resulting in more than 400 satisfying conditions. Most of this is about how to get a system time above 10 milliseconds, or how to make a thread sleep less than 10 milliseconds. In this article I will focus on why it is so difficult to get a system time that is more than 10 milliseconds accurate. You might think that with the GetSystemTime API it's easy to solve the problem, and this API function returns a SYSTEMTIME structure that contains a wmilliseconds domain, which in the MSDN documentation says it saves the current millisecond time. But it's not as simple as it really is. So what's the accuracy of using getsystemtimeasfiletime to get 100 nanoseconds? Let's start with a little experiment: try to get the system time again, format it and output it to the screen (seeFigure 1)。
My goal is not nanosecond, but only millisecond precision, and it should be able to judge from the SYSTEMTIME structure. Let's take a look at the output:
20:12:23.47920:12:23.47920:12:23.49420:12:23.494[... A lot of them have been removed ...] 20:12:23.49420:12:23.50920:12:23.50920:12:23.509 ...
As you can see, the best accuracy I could get is 15 milliseconds, which is the length of the Windows NT clock cycle. Windows NT will update the system time for each clock cycle. The Windows NT scheduler also starts abruptly and may choose a new thread to execute. For more information on this, see theInside WindowsThird edition (Microsoft press®, 2000), the author is David Solomon and Mark Russinovich.
If you run the code that I just showed, you might see that the time is updated about every 10 milliseconds. If that's the case, it might mean that you're running Windows NT on a single-processor machine with a clock cycle typically of 10 milliseconds. As you can see, in this approach, the system time update frequency is not fast enough to be a technology for me. Let's try to find a solution.

the initial attempt
When you ask how to get a better system time than 10 milliseconds, you might get the following answer: Use performance counters and synchronize the performance counter values with the system time that changes immediately. Combine these values to calculate the current time with a very high precision.Figure 2Shows the implementation method.
Performance counters are a high-precision hardware counter that measures a short cycle time with high accuracy and low overhead. I have been synchronizing the performance counter values and the corresponding system time in a compact cycle, waiting for the system time to change. When the system time changes, I save the value of the counter and the system time.
With these two values as a reference, it is possible to calculate a high-precision current system time (seeFigure 2Get_time), look at the results:
... 21:23:22.29621:23:22.29721:23:22.29721:23:22.29821:23:22.29821:23:22.29921:23:22.30021:23:22.30021:23:22.30121:23:22.3 0121:23:22.30221:23:22.30221:23:22.303 ...
Although it looks very successful, there are several problems with this implementation: synchronous implementation (a good reason the function is named "Simplistic_synchronize"), the frequency of the QueryPerformanceFrequency report, and the lack of protection for system time changes. In the following chapters, we will consider some of the possible improvements to these issues.

a reliable way to achieve synchronization
The synchronization implementation does not take into account the Windows NT Scheduler preemptive issue. For example, it cannot guarantee that a thread context switch will not occur between the following two lines of code, resulting in a delay of an unknown time period:
:: Getsystemtimeasfiletime (&FT1);:: QueryPerformanceCounter (&li);
Most of the time, as long as the following conditions are met, this overly simplistic synchronization function is still successful:
  • The current thread will not be preempted to a ready state by a higher-priority thread;
  • The current thread's time slice never ends
  • Very few hardware interrupts (unlike clock interrupts themselves)
    For this reason, the most concise solution is to elevate the priority of the process to realtime_priority_class, raising the priority of the thread to thread_priority_time_critical, thereby preventing the thread from being preempted during synchronization. Unfortunately, you have nothing to do with hardware interrupts, but well-behaved drivers should handle their interrupts, queued deferred process calls (deferred procedure call, DPC), and even process DPC in microseconds order. The problem is that you cannot guarantee that all drivers in the system are behaving well. In fact, you'll still have a lot of interruptions, even if you have a well-behaved driver in your system.
    Nonetheless, there is a reliable synchronization method that does not have to elevate the priority of processes and threads. Figure 3is aThis method implements the basic flowchart of the steps.


    Figure 3 Reliable Synchronization

    You need to constantly check to see if the system time is changing, just likeFigure 2As shown in the Simplistic_synchronize implementation. The biggest difference from the previous implementation is that you should now use the performance counter itself to verify that you are always at the exact level you want. It sounds simple, but a closer look at Figure 3 will tell you that it's not as simple as you might think. Further explanations are needed, such as why you save the difference between the last two values of the performance counter in the Prev_diff variable. The reason is that from the point where the system time is saved to T1 to the point where the counter value is saved to P1, the system time can potentially change without being detected until the next internal loop is executed (to detect a change in system time).
    Next, you may mistakenly assume that the time has changed between the latest two counter values (note: p1->p0), but not actually. To ensure this, you should assume that the system time changes between the latest two counter values (note: Prev_diff), or between the previous two counter values (except for an event that is not possible within the loop-the event has changed the start time through the internal loop). At the end of the synchronization, a counter value adjustment is implemented, which guarantees that the return value can be within the desired precision. Figure 4 shows the process.


    Figure 4 Calculation

    This synchronization method requires several iterations to complete, but it does not actually prove to be a problem. For more information about synchronization and its accuracy, you should look at this article subtitle"Sync: how good?" "。

    frequency Problem
    Although we have a good start, there are still some problems to be solved. Let's say that you perform this synchronization in a timely manner in certain sections. Then, whenever you need a high-precision time, call Get_time. As previously described, the frequency of the Queryperfomancecounter report is used to calculate the current system time with high accuracy. The time reported by Get_time is bound to deviate greatly from the actual clock time, resulting in a much larger value than the accuracy you obtained. This is because performance counters are not inherently used to measure long cycle times.
    I conducted a small test to examine how large this effect would be, with 2 microseconds as an acceptable synchronization limit. (I chose 2 microseconds because I had the best results for my dual-PII 400HZ CPU), Figure 5 shows the result.


    Figure 5 Synchronization Test

    The test results show that my high-precision clock deviates from the actual system time by 1 milliseconds in just 110 seconds. Calculator indicates that there is an error of about nine out of 10,000 in the performance counter frequency report. A 0.000009 error sounds tiny, but if you want to report a time below a microsecond this is a big shock. At first, I had two ideas. First, the user is responsible for regular resynchronization, and it must be decided how long it takes to do it. Second, synchronization is performed by a background thread every n seconds.
    Before I tested the first idea, I decided against it. The second idea seemed more feasible, and the only problem I could foresee was that the necessary synchronization between the client and the synchronization thread would incur some overhead. The simplicity of use always adds complexity and overhead.
    The download example provided in this article enables the use of a background thread to synchronize performance counters and system time. Figure 6 Explains how this implementation managed to keep itself close to the actual system time (note that the ordinate is now set to +/-100 microseconds).


    Figure 6Synchronization examples

    Figure 6 shows a high-precision time deviation from the system time for a 13-minute period. The Blue line shows the case where a periodic resynchronization is applied before the deviation value reaches the allowable system time deviation (50 microseconds in this case). It also indicates the time between synchronizations after each execution increases the value. This is because the currently implemented time provider adapts to the frequency metering errors reported by the performance counters and is constantly applied to internal high-precision time calculations.
    Although the Blue line shows the data with smoothing filtering applied, the yellow line shows the original data that deviates from the system time. This filtering is done in real time, and this is the data that is actually used to determine the true frequency of the performance counter and the deviation between the high precision time and the system time. For more details, please see the source code for the download.

    prevent system time from being changed
    There is also the problem of system time change. Whenever this happens, you must synchronize immediately to ensure that the calculation time is correct. This is not difficult under Windows 2000 and Windwos XP, because whenever the system system time is set, the system always broadcasts a wm_timechange message to all the top-level windows. Unfortunately, this is not mandatory in Windows NT and earlier versions, although it is true in the SDK documentation that the application should send this message to all top-level windows after changing the system time. Note that this sentence uses "should", so you cannot rely on everyone to do so.
    To get a thorough understanding of the problem, I should say that changing the system time is not something special for any application. In order to change the system time or related configuration, you need to enable se_systemtime_name priority. If the user does not enable this right, you can run the program under an administrator account, to install the program as a Windows NT service by the Administrator, or to have the administrator give the account that runs the program a required permission. For example, for Windows NT 4.0, what you want most is that the system administrator does not or does not allow the installation of sick programs (that is, changing the system time without knowing other applications).
    So how do you actually handle wm_timechnage messages? Now that you have a thread for periodic synchronization, the only thing you need to do is to have your thread create an invisible top-level window, and run a message loop in addition to regular synchronization.

    Time Adjustment
    There is another problem with Windows NT maintenance system time. To help software such as network time Protocol, NTP, clients keep time synchronized with external resources, Windows NT exposes a setsystemtimeadjustment API. This API has two parameters, the time regulator itself in 100 nanoseconds, and a Boolean value that indicates whether Windows NT disables the time adjuster. When the time adjuster is enabled, the system adds the value of the specified time regulator when each clock is interrupted. When disabled, the system replaces it with the addition of the default time increment (which in this article is the same as the clock interval), and more details are available in the Platform SDK documentation.
    But there are two more questions. First enable (change) the time regulator to change the reference frequency-the time flow. Second, it is also a big problem, that is, when the system time is modified, the system does not send enable or disable notifications. Changing the time regulator on a system even with a minimum of 156,250 units (1 units 100 nanoseconds) of the default time increments will result in a change in the reference frequency of 6.4PPM (1/156250). Again, it may not sound like much, but consider your need to stop the system from changing over a period of 50 microseconds, which means that you will exceed the limit after a few seconds without resynchronization.
    To reduce the impact of such adjustments, the time provider must monitor the settings of the current time regulator. Without the help of the operating system itself, by calling Setsystemtimeadjustment's partner API Getsystemtimeadjustment. You can avoid straying too far from the system by constantly performing this check within a sufficiently short interval and adjusting the internal frequency as needed.

    Time Supply
    Now that you have a good understanding of the various aspects of the problem, I will give a brief introduction to the Time_provider class in the download code. This time-supply is implemented in a parameterized single-mode manner, providing customers with a high-precision, continuous-updating time:
    template<         TypeName Counter_type,         int keep_within_micros = +,         int synchronize_thread_priority = Thread_priority_below_normal,         int tuning_limit_partsperbillion = +,         int max_wait_millis = 10000,         int Min_wait_millis = 100>class Time_provider
    Using the time provider to get the current time is similar to using the Windows API:
    typedef hrt::time_provider
       Figure 7Explains the template parameters, type definitions, and member functions available in the Time_provider class. You may be surprised to find that different tuning parameters are specified as template parameters. From my point of view, they are all design parameters and can be determined at compile time according to the needs of your application.
       Figure 8The code demonstrates the use of the Time_provider class to collect an example of the original time in a small loop, and then convert and output. In the downloaded source code you can find another example that uses multithreading (the same idea is demonstrated in a multithreaded environment).

    Performance Factor
    So how much does it cost to use the Time_provider class to get system time? When you have to calculate time and not just get time, some extra work is unavoidable. If you really care about the performance in some of the critical sections of the code, use theFigure 8The technology that involves the original counter value is shown in. Using raw values allows you to delay system time conversions, which does not immediately incur additional overhead (except for the value of the call collection counter itself, which, of course, is unavoidable).
       Figure 9is clearly shown in the table, it gives a performance evaluation of the Win32 API in relation to the Time_provider. The number in the table is relative to the percentage of getsystemtime execution time on the Windows XP symmetric multiprocessor (SMP) system (the number in parentheses corresponds to a single-processor system).
    As I mentioned earlier in this article, the cost of invoking QueryPerformanceCounter is not negligible, especially for single-processor systems. The execution time of using Performance counter API calls is usually much faster on symmetric multi-processing systems (SMP). This is because the performance counters of most symmetric multi-processing systems implement the Pentium Timestamp counter (time stamp counter, TSC), which is relatively inexpensive compared to a single-processor system implementation.
    I'm a little bit disappointed with performance, even without trying to optimize the calculations. For better performance and loss of portability, you might try to use a different counter. The Time_provider class is parameterized on the counter type and can be used with other high-precision counters. The downloaded source also has another experimental class tsc_counter, which can be used directly from the Pentium TSC. Preliminary testing of this class shows that it has much better performance than using Performance counter APIs, even (compared to performance counters) on SMP machines. When doing withFigure 9In the same test, the Tsc_counter version of the time supply clock is at 33% (file time), 133% (System time), and 5.9% (original time).

    Future Direction
    There are many potential problems with the current implementation--not surprisingly, given the complexity of the problem. Because of the problems caused by hardware compatibility, the code can not be used on any available system, such as power saving, CPU overclocking, and non-persistent counters. If you find a way to make the supply more reliable under these conditions, let me know. You should know your hardware platform before deciding to use the code.
    Packaging for. NET and COM is certainly possible, allowing time providers to be used in languages other than the C + + language. In fact, I have implemented a time provider as a COM in-process server.

    Conclusion
    If you now think you can get almost arbitrary precision system time, give a little warning: Don't forget the preemptive multitasking system like Windows NT, the best case is that you get a timestamp that reads only the time that the performance counter takes and translates the read content into an absolute time difference. In the worst case, time loss can easily reach as much as dozens of milliseconds.
    While this may presage that everything you do is useless, it is not necessarily true. Even if the call to Win32 API getsystemtimeasfiletime (or gettimeofday under Unix) is also subject to the same conditions, you will not actually do much worse than that. In most cases, you will get good results. Just don't make any real predictions about Windows NT-based timestamps.

    Background Knowledge
      • time Function (Functions) :Inside Windows 2000, third edition , author David Solomon and Mark Russionvich (Microsoft Press, 2000)
      • Performance counter values may jump forward unexpectedly (performance Counter value might unexpectedly Leap Forward)

    Conclusion
    Some descriptions of performance counters (performance Counter):
    In some computer hardware system, it contains high precision running counter, it can obtain high precision timing interval, its accuracy is related to CPU clock frequency. The steps to take this approach are as follows:

      • 1, first call the QueryPerformanceFrequency function to obtain a high precision running counter frequency f. The unit is the number of times per second (N/S), which is generally very large.
      • 2. Call QueryPerformanceCounter at both ends of the code that needs to be timed to get the numeric n1,n2 of the high-precision running counter. The difference between the two values is converted by F to the time interval, t= (N2-N1)/F.
About the author
  Johan Nilsson is a system engineer at the Swiss space company in Esrange, located above the Arctic Circle. Since the release of Windows NT 4.0, he has been using C + + to develop software for Windows NT, programming Windows/dos from Windows 3.1. Contact him:[email protected]
 


This paper is translated by Vckbase MTT

Figure 1 obtaining and outputting system time

#include <windows.h> #include <iostream> #include <iomanip>int main (int argc, char* argv[]) {  SYSTEMTIME St;  while (true)  {    :: GetSystemTime (&st);    Std::cout << STD::SETW (2) << st.whour << ': '              << std::setw (2) << st.wminute << ': '              << STD::SETW (2) << st.wsecond << '. '              << STD::SETW (3) << st.wmilliseconds << '/n ';  }  return 0;}

Figure 2 initial attempt

#include <windows.h> #include <iostream> #include <iomanip>struct reference_point{filetime file_ Time Large_integer counter;}; void Simplistic_synchronize (reference_point& ref_point) {FILETIME ft0 = {0, 0},ft1 = {0, 0}; Large_integer li;////Spin waiting for a change in system time. Get the matching//performace counter value for that Time.//::getsystemtimeasfiletime (&ft0);d o{:: Getsystemtimeasfiletime (&AMP;FT1);:: QueryPerformanceCounter (&li);} while (ft0.dwhighdatetime = = ft1.dwhighdatetime) && (ft0.dwlowdatetime = = Ft1.dwlowdatetime)); ref_point.file _time = Ft1;ref_point.counter = li;} void Get_time (Large_integer frequency, const reference_point&reference, filetime& current_time) {LARGE_ INTEGER Li;::queryperformancecounter (&li);////Calculate performance counter ticks Elapsed//large_integer Ticks_ Elapsed;ticks_elapsed. QuadPart = li. quadpart-reference.counter.quadpart;////Translate to 100-nanosecondsintervals (filetime//resolution) and add to//reference FILETIME to get current Filetime.//ularge_integer filetime_ticks, Filetime_ref_as_ul;filetime_ticks. QuadPart = (Ulonglong) (((double) ticks_elapsed. quadpart/(double) frequency. QuadPart) (*10000000.0) +0.5); Filetime_ref_as_ul. Highpart = Reference.file_time.dwhighdatetime;filetime_ref_as_ul. LowPart = Reference.file_time.dwlowdatetime;filetime_ref_as_ul. QuadPart + = Filetime_ticks. quadpart;////Copy to Result//current_time.dwhighdatetime = Filetime_ref_as_ul. Highpart;current_time.dwlowdatetime = Filetime_ref_as_ul. LowPart;} int main (int argc, char* argv[]) {Reference_point ref_point; large_integerfrequency; Filetimefile_time; Systemtimesystem_time;::queryperformancefrequency (&frequency); simplistic_synchronize (Ref_point); while (true {get_time (frequency, Ref_point, file_time);:: FileTimeToSystemTime (&file_time, &system_time); std::cout << STD::SETW (2) << system_time.whour << ': ' << std::setw (2) << System_time.wminute <&lt ; ': ' <<STD::SETW (2) << system_time.wsecond << ': ' << std::setw (3) << System_time.wmilliseconds < < '/n ';} return 0;}

Figure 7 Time_provider parameters and members

Template parameters
The Counter_type represents a high-precision, high-frequency counter. It must provide static member values and frequencies as defined by the Value_type definition.
Keep_within_micros defines the maximum number of microseconds that a time provider can deviate from the actual system time. It also affects the synchronization frequency of the resynchronization thread.
synchronize_thread_priority defines the priority that the synchronization thread should set when it performs synchronization. This should not be modified unless your program is constantly executing on a high priority level. The default is Thread_priority_below_normal, which does not disturb normal execution of normal or high-priority threads.
The implementation of the Tuning_limit_partsperbillion Current time supply is a continuous measurement counter frequency. This frequency is maintained internally, allowing for less frequent resynchronization and more accurate timing. When the accuracy of the measured frequency reaches a certain threshold, no adjustment is performed (but periodic resynchronization is always active). The unit of this limit is the error rate of the calculation frequency, and the corresponding default value is every 1,000,000,100 units.
The max_wait_millis defines the maximum tuning interval allowed, in milliseconds-that is, the wait time to check how far the high-precision time deviates from the system time. The tuning interval is automatically adjusted, but this limit can only be reached. This parameter should not be modified in general.
Min_wait_millis defines the minimum allowable tuning interval, in milliseconds. See Max_wait_mills for details.
Type definition
Raw_value_type ability to store "raw" timestamp types
member functions
instance returns a reference to a unique instance of this class
systemtime Returns the current system time, in the form of a SYSTEMTIME structure
filetime Returns the current system time, format is FILETIME structure
rawtime Returns the current system time, returning the "original" timestamp with the smallest load. In order to convert it to absolute time using filetime_from_rawtime or Systemtime_from_rawtime
Systemtime_from_rawtime The "original" timestamp to absolute time, denoted by the SYSTEMTIME structure
Filetime_from_rawtime The "original" timestamp to absolute time, denoted by the FILETIME structure



Figure 8 using the Time_provider class

#include 


Figure 9 Win32 time function and performance

Win32 API Execution Time Time_provider Execution Time
Getsystemtimeasfiletime 1.9% (~0%) Filetime 135% (900%)
GetSystemTime 100% (100%) SystemTime 234% (1001%)
QueryPerformanceCounter 55% (400%) Rawtime 57% (400%)


Sync: How good is it?
Using the synchronization method I described in this article, you can specify the accuracy of the results you want. However, in fact, the quality of the results you can get is limited by platform dependencies (hardware and software). The clock interrupt processor in Windows NT takes time to execute, which greatly limits your precision to the time it takes to execute the clock interrupt processor, plus the thread context switch time, and the time that the function is called to check when time changes. If you are running on a symmetric multi-processing (SMP) machine, you can avoid clock interruptions by running synchronization threads on another CPU.
On SMP machines, it is forbidden to run synchronous threads that can produce a dozens of times-fold difference in synchronization accuracy on CPUs that handle clock interrupts. The only problem is that you have to first know which CPU is processing the actual clock interrupt. From my limited experience, I can only tell you that it seems to be a cpu#0 (I think it feels a little strange). Assuming this is true, you can just use the Setthreadaffinitymask API to remove cpu#0 from the list of threads that allow the processor. You should verify that the process is allowed to run on another processor by pre-checking the result of the getprocessaffinitymask call.

http://blog.csdn.net/jiangxinyu/article/details/2728416

Timers: A continuous update for Windows, a high-precision time provider

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.