C++11’s Chrono: Learning about C++11’s Time(ing) features

So, the <chrono> header classes and functions are a bit of a confusing mess.  Different clock types, different duration formats, and weird-a$$ syntax (which is more common as C++ gets more features) all combine to make it a confusing mess to actually use calculations in code.

However, since it’s pretty important to get used to new C++11 features that can make cross-platform development easier, this is one area that deserves further study…

Luckily, one of my favorite reference books (which includes C++11 coverage) has a section that’s actually available online that helps illuminate this mysterious area: 5.7 Clocks and Timers | The C++ Standard Library – InformIt (Nicolai M. Josuttis).  Still, that’s not for the faint of heart.  It requires a few rereads..

Alright, so using my understanding of how this all works, lets break down the 3 logical components of time in <chrono>:

  • duration: a measurement of time as in “length of n ‘ticks’ over a particular time unit”. This can be represented as milliseconds, seconds, minutes, etc.  The calculations of duration revolve around ratios (i.e. 1/1000 for millisecond) and an integral or floating point type used to formally represent the duration when queried. (For more on ratios, also see the above link’s previous page, 5.6 Compile-Time Fractional Arithmetic with Class ratio<>.
  • timepoint: a point in time expressed in n ‘ticks’ of duration from a special base point (“beginning of time”, or the earliest representable form of time on the computer [based on which clock is being referred to]). This class is templatized by both a clock and a duration.
  • clock: a logical “clock” that represents the ever-changing motion of time.  There are 3 of these, one of which represents the ‘official’ O/S clock (system_clock), another which is guaranteed to move forward at a predictable rate [steady_clock] (unlike the official clock which can be manipulated by outside forces), and a low-level measurement of time useful for fine-grained measurements (high_resolution_clock).
    Since these are the foundation of every calculation and manipulation, they contain representations of a duration and time_point, and of course a function indicating what time it is ‘now()’.

So, hopefully that is understandable.  We need at least a basic grasp of these features to utilize time-based measurements and manipulation in our programs.

The next step would be to provide some real-world use of this stuff to get this ‘visually’.  So, for example, say we wanted to I dunno, time a piece of code.  We’d need a good unchangeable clock to do this, of course, so steady_clock and high_resolution_clock are our only options.  Since it is well, code, we’ll need a fine-grained measurement, so high_resolution_clock, here we come!

Now, on with the ugly syntax!  The predefined class for our chosen clock the high_resolution clock is:

std::chrono::high_resolution_clock;

To make things simpler in code, its probably best to use type aliasing.  So let’s make it ‘hrClock_t’:

typedef std::chrono::high_resolution_clock hrClock_t;

Now, since we’ll be needing a starting time point, we’ll have to ask that clock what the time is ‘now’:

auto clkStart = hrClock_t::now();

While ‘auto’ helps make coding easier, it may hinder understanding of what’s going on.  So, let me reassure you: we are getting a time_point:

// more verbose version of the above:
std::chrono::high_resolution_clock::time_point = std::chrono::high_resolution_clock.now();

Okay, that’s all well and good.  We now have a starting time point, and will eventually need an end time point.  So, after running through the test code, we’ll gather a second time_point:

auto clkEnd = hrClock_t::now();

Next up: Actually figuring out how much time went by, in a format we desire.  So here comes duration. We’re going to typedef this one so it doesn’t give us carpal tunnel, and then do the assignment:

typedef std::chrono::duration<double, std::milli> millisec_t;
millisec_t clkDuration(std::chrono::duration_cast<millisec_t>(clkEnd - clkStart));

Okay.. wow. Wtf is up with that code?  Well, yes that’s a bit confusing at first, so let’s see what’s happening here:

Each time_point is specific to it’s clock – in this case, the high_resolution_clock.  What we want, however, is that time converted to milliseconds (1/1000th of a second).  To do that, we first make the time_point calculation (end-start) and put it into a special template function that will do what we TTIT (‘template-tell it to’?).

And what we are telling it with ‘duration_cast‘ is that we need a duration of the form <double, milli> (millisec_t remember).  That tells the compiler to generate code that converts ‘time_duration’ into something representable in thousandths of a second (1/1000), using floating point math to achieve the result.  Of course, the floating point return stored in clkDuration is double as that was the first template parameter to our millisec_t duration type.  The only thing left to do is actually grab that value from clkDuration.  One simple line retrieves the ‘elapsed time’ double from clkDuration:

double fElapsed = clkDuration.count();

Are we any closer to understanding how this works?  I know I am.  But hey,  I’m typing up a blog post while working out the details.. so maybe I’m a bit biased.

But, as examples are one of the best forms of understanding things, let me direct your attention to a simple ‘cout vs. printf benchmark’ @ Coliru. (also available at Pastebin)

Oh, just a couple of more notes:

  1. There are some predefined duration typedefs, which you may prefer to use over your own.  For our example, there is a std::chrono::milliseconds typedef in the headers which may or may not suit you – it’s templated as duration<int_type, milli> so you lose floating point precision in using it.  The list of the rest is available on the link above to section 5.7 of the C++ Standard Template Library excerpt.
  2. There’s a handy little feature for inserting ‘sleeps’ in your code by using sleep_for with durations.  It’s normally covered in C++11 ‘concurrency’ articles & references, but it may prove useful in other scenarios.  If you program in Windows you are probably most certainly familiar with ‘Sleep’.  Well, the cross-platform compatible version of that would be (note this requires header <thread>):
    std::this_thread::sleep_for(duration);

    with an example being:

    // sleep (pause execution) for 10 ms
    std::this_thread::sleep_for(std::chrono::milliseconds(10));

So, there it is.  Hope that helped somebody understand one of the new useful (but cryptic) features of C++11!

(by the way, there’s other feature of clocks and durations that I didn’t cover – calculating future times, waiting until a specific time, etc.. but that’s something you can discover on your own =)

Advertisements