3
\$\begingroup\$

I'm attempting to implement a high resolution class used to time the execution time of functions in Windows. After using this documentation, I have come up with the following:

#include <iostream>
#include <windows.h>

class Timer {

private:

    LARGE_INTEGER start_count;
    LARGE_INTEGER stop_count;

    LARGE_INTEGER tick_freq;

public:
    // Query the tick frequency upon instantiation of the class
    Timer() {
        QueryPerformanceFrequency(&tick_freq);
    }
    // Query performance counter at the start of a block of code
    void start() {
        QueryPerformanceCounter(&start_count);
    }
    // Query the performance counter at the end of a block of code, then calculate time elapsed in seconds
    double stop() {
        QueryPerformanceCounter(&stop_count);
        return (double)(stop_count.QuadPart - start_count.QuadPart) / tick_freq.QuadPart;
    }
};

int main() {
    Timer t;
    t.start();
    // code to be timed
    std::cout << t.stop();
}

I understand that there would be an error of plus or minus the period of each tick. However, various functions I have timed seem to give unexpected results. For example, timing a function that finds the greatest common denominator using Euclid's algorithm consistently uses 100 or 200ns, but appending one int to a vector takes 1600ns. This therefore leads me to the question: how accurate is this timing class, and what can I do to improve it?

\$\endgroup\$
4
  • 2
    \$\begingroup\$ Raymond Chen from Microsoft wrote up a devblog about it a couple years ago: devblogs.microsoft.com/oldnewthing/20170921-00/?p=97057 "As accurate as the hardware allows" for those particular functions \$\endgroup\$ – Casey Mar 6 at 4:07
  • 1
    \$\begingroup\$ It may not be your Timer class...When you timed your functions, did you warm the cache by running the function a few million times before beginning an actual timing and avoid including output in the duration? \$\endgroup\$ – Casey Mar 6 at 4:13
  • 2
    \$\begingroup\$ Also, don't profile Debug builds. Make sure you're in Release mode. \$\endgroup\$ – Casey Mar 6 at 4:13
  • \$\begingroup\$ @Casey thanks for the tip about warming the cache, will do in the future. \$\endgroup\$ – George Tian Mar 7 at 1:38
2
\$\begingroup\$

Avoid storing data unnecessarily

Your class stores the start time, stop time and frequency in member variables, however you only need to store the start time:

class Timer {
    LARGE_INTEGER start_count;

public:
    // Query performance counter at the start of a block of code
    void start() {
        QueryPerformanceCounter(&start_count);
    }

    // Query the performance counter at the end of a block of code, then calculate time elapsed in seconds
    double stop() {
        LARGE_INTEGER stop_count;
        QueryPerformanceCounter(&stop_count);

        LARGE_INTEGER tick_freq_count;
        QueryPerformanceFrequency(&tick_freq);
        return (double)(stop_count.QuadPart - start_count.QuadPart) / tick_freq.QuadPart;
    }
};

Consider using std::chrono functions

If you can use C++11 or later, then you should be able to get the same timing information in a platform-independent way by using std::chrono::high_resolution_clock. See this example for how it can be used.

Accuracy

The actual accuracy depends on many things. The inherent accuracy depends on the accuracy of the performance counter of the CPU, which can vary between models. However, there are many reasons why the measured time will vary, among them are:

  • The state of the cache (as suggested by Casey, you should do a warm-up run before doing the actual performance measurements)
  • The CPU frequency changes (the OS will normally adjust the frequency many times per second to ensure your processor runs as energy efficient as possible)
  • Other threads might get a timeslice while your code is running

You could try to address all these issues yourself (many of them are solved by doing a warm-up run, then running your performance measurements many times and taking the average, removing outliers in some way). Alternatively, consider using existing libraries that already solve this problem, for example Google Benchmark.

\$\endgroup\$
3
  • \$\begingroup\$ Thank you for the link to Google Benchmark. My reasoning behind storing the stop time and frequency in private variables was that it would prevent additional time being used in declaring local variables in the stop function and calling the Windows functions to get their values. Would the time cost of the above be miniscule enough to justify a better coding style as you suggested? \$\endgroup\$ – George Tian Mar 7 at 1:41
  • \$\begingroup\$ @GeorgeTian FYI, the std::chrono::high_resolution_clock in all cases if just a typedef for one of the other two clocks. Don't let the implementer pick. Just use std::chrono::steady_clock \$\endgroup\$ – Casey Mar 7 at 2:46
  • \$\begingroup\$ @GeorgeTian Declaring a LARGE_INTEGER locally does not have any runtime cost. \$\endgroup\$ – G. Sliepen Mar 7 at 8:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.