Introduction
This is a very simple, header-only class that can be used for high-resolution timing.
Background
I first posted an article about this class at this site's predecessor (codeguru.com) about twenty years ago. Since then, I have seen many implementations of timers using the QueryPerformance
... family of functions, but none of them work the way I like them to. Where this differs from those is in its simplicity of use. One timer instance can be used for an entire multi-threaded application.
The methods of this class, once initialized, are fully reentrant. Since one instance can suffice, there should be only one initialization and that is at construction. This class retains only three pieces of information that are all set at construction so it is very lightweight. They are the frequency of the counter, the period of the counter, and a flag indicating the availability of the performance counter. I have never encountered a desktop or laptop machine where it is not available but the flag is there for verification. While only one instance of this timer is necessary, it is light enough that construction is a short process so multiple instances can be used if preferred.
This class works by acquiring the value of the Windows performance counter and subtracting that value from one previously obtained. That difference is the number of counter ticks that have elapsed between calls of the Since
method. The number of ticks is then converted to a time duration by multiplying it by the period of the counter which is the reciprocal of its frequency. Over the years, I found the counter frequency to range from about 1MHz in early Windows NT releases to 10MHz on current machines.
Using the Code
Here is a listing of the class. As mentioned, it is a header-only class.
#pragma once
#define ELAPSED_H
#include <ProfileAPI.h> // for QueryPerformance... functions
class Elapsed
{
public :
Elapsed() {
LARGE_INTEGER li;
if( QueryPerformanceFrequency( &li ) )
{
m_Available = true;
m_Frequency = li.QuadPart;
m_Period = 1.0 / (double)m_Frequency; }
}
inline double Since( double begin=0 )
{
LARGE_INTEGER endtime;
QueryPerformanceCounter( &endtime );
return ( endtime.QuadPart * m_Period ) - begin;
}
bool IsAvailable() const { return m_Available; }
INT64 GetFrequency() const { return m_Frequency; }
double GetPeriod() const { return m_Period; }
protected :
bool m_Available { false };
double m_Period { 0 };
INT64 m_Frequency { 0 };
};
Here is a snippet of code that shows it in action. This illustrates how one can measure the overhead of calling the Since()
function to measure the elapsed time.
void MeasureTimerOverhead( int loopCount )
{
double temp = 0;
Elapsed timer;
start = timer.Since( 0 );
for( int n = 0; n < loopCount; ++n )
{
temp = timer.Since( start );
}
double elapsed = timer.Since( start );
printf( "time for Since calls was %9.6f seconds\n", elapsed );
double average = 1.0E6 * elapsed / loopCount;
printf( "average time for Since : %9.6f microseconds\n", average );
}
Executing this function on my machine, an i9-9900X at 3.5GHz, for 40M loops results in an average time of 0.0163 microseconds or about 16 nanoseconds. This includes the time taken to increment and compare the loop's iteration counter but other experiments indicate this is a small fraction (< 1%) of the total time.
Points of Interest
My testing has demonstrated the call to the timer's Since
method has very low overhead. However, since the counter's frequency is in the megahertz range, I consider the timer's accuracy to be in the range of tens of microseconds. My reasoning is a 10MHz counter has a period of 0.1μS which is 1% of 10μS so you will be reasonably accurate when timing values of 10μS or 0.01mS and higher. Yes, I know this is not a rigorous analysis but I consider it useful to know at least what the accuracy's order of magnitude is. I rarely need to measure less than tenths of milliseconds so this timer has been very useful for me. I hope it is for you too.
History
- 19th December, 2019: Initial submission