Search code examples
c++c++11boosttimer

Comparison of `std::chrono` clocks with `boost::xtime`


How do the C++11 std::chrono clocks steady_clock and high_resolution_clock compare with boost::xtime::xtime_get() in terms of quirks and general properties on various platforms?

The standard does not guarantee that high_resolution_clock be steady (it explicitly mentions it may be an alias to system_clock), so that's one pitfall to look out for. Other properties that come to my mind:

  • Resolution: The C++11 standard does not seeem to guarantee any resolution; what are the "real-life" resolutions of these clocks? How does boost::xtime_get() fare on the same systems?

  • Maximum Duration: I know that e.g. clock() breaks down after roughly an hour on systems with a 32-bit clock_t and a 1 MHz nominal clock resolution. (Yes, I know clock() is supposed to do a somewhat different job.) Can the C++11 standard clocks cope with durations in the order of days, maybe even weeks on all known platforms?

  • Any other known issues or surprising quirks (Edit: of either std::chrono clocks or boost::xtime::xtime_get)?


Solution

  • How do the C++11 std::chrono clocks steady_clock and high_resolution_clock compare with boost::xtime::xtime_get() in terms of quirks and general properties on various platforms?

    Any timing library can only deliver what the underlying OS/hardware combination can deliver -- full stop.

    Even if the library API promises nanosecond resolution, that doesn't mean that the underlying OS/hardware can deliver that precision. So ultimately the timing API can not improve upon the quality of the platform.

    boost::xtime is basically what was standardized by C (and subsequently C++) as timespec. This is a {second, nanosecond} pair which is used both as a point in time, and as time duration, depending on the function it is used with in the standard C headers. Though a quick survey of boost headers appears to use xtime only as a time point (I could've missed something).

    timespec has a long history of existing use, especially in POSIX systems. It has been in existence on POSIX systems much longer than std::chrono, which was designed in 2008, and standardized in C++11 (2011).

    The range of timespec (xtime) is typically larger than the age of the universe. Though on systems that can not supply a 64 bit integral type, the range of timespec will be significantly smaller: +/-68 years, typically centered around 1970 when it is used as a time point.

    As mentioned above, timespec advertises nanosecond precision on all platforms, but will only deliver the precision that the underlying platform can supply.

    chrono supplies separate types for time points and durations. This helps catch errors at compile time. For example if you add two time points together, it doesn't compile. 9am today + 7am today is nonsensical. However if you subtract two time points, that makes perfect sense and returns a separate type: a duration. 9am today - 7am today is 2 hours.

    chrono supplies multiple types for both durations and time points which can differ in both precision and representation. The "built-in" durations are nanoseconds, microseconds, milliseconds, seconds, minutes and hours, each represented with a signed integral type (that list is expanded in the C++20 spec). But you can create your own duration types with your own precision and representation (e.g. floating point, or a safe-int library).

    The implementor of chrono for any given platform can advertise the precision of the platforms "now()" function. I.e. it doesn't have to always be nanoseconds, it might be microseconds, or some other unit. The vendor isn't required to be honest, but they usually are. Clients can query the return type of now() for its precision, programmatically, at compile time (this is C++ after all).

    The chrono data structures are {count of units}, as opposed to the xtime {seconds, nanoseconds} data structure. For chrono this is true of both durations and time points, even though these are distinct types.

    The {count of units} layout has several advantages over the {seconds, nanoseconds} layout:

    • There is an opportunity to have a smaller sizeof. system_clock::time_point is typically 64 bits while xtime is typically 128 bits. This does give xtime a superior range. However the chrono library can also be used with 128 bit integral types which will subsequently have a larger range than xtime's.

    • Clients can make the size/range tradeoff with chrono. xtime clients get what they get.

    • Arithmetic is faster/more-efficient and easier to program with the {count} data structure than with {seconds, nanoseconds}. This leads to code that is smaller, faster, and generally more bug free (negative values represented with {seconds, nanoseconds} are an ongoing horror story).

    • For a given sizeof and precision, one can always get a larger range with a {count} data structure than a multi-field data structure such as {seconds, nanoseconds}.

    The standard does not guarantee that high_resolution_clock be steady (it explicitly mentions it may be an alias to system_clock), so that's one pitfall to look out for.

    In practice high_resolution_clock is always a type alias for either steady_clock or system_clock. Which, depends on platform. My advice is to just use steady_clock or system_clock so you know what you're dealing with.

    Resolution: The C++11 standard does not seeem to guarantee any resolution; what are the "real-life" resolutions of these clocks?

    The advertised resolutions are:

    libc++/llvm:
    
    system_clock
        rep is long long : 64 bits
        period is 1/1,000,000
        is_steady is 0
    
    high_resolution_clock
        rep is long long : 64 bits
        period is 1/1,000,000,000
        is_steady is 1
    
    steady_clock
        rep is long long : 64 bits
        period is 1/1,000,000,000
        is_steady is 1
    
    high_resolution_clock is the same type as steady_clock
    
    libstdc++/gcc:
    
    system_clock
        rep is long : 64 bits
        period is 1/1,000,000,000
        is_steady is 0
    
    high_resolution_clock
        rep is long : 64 bits
        period is 1/1,000,000,000
        is_steady is 0
    
    steady_clock
        rep is long : 64 bits
        period is 1/1,000,000,000
        is_steady is 1
    
    high_resolution_clock is the same type as system_clock
    
    VS-2013:
    
    system_clock
        rep is __int64 : 64 bits
        period is 1/10,000,000
        is_steady is 0
    
    high_resolution_clock
        rep is __int64 : 64 bits
        period is 1/1,000,000,000
        is_steady is 1
    
    steady_clock
        rep is __int64 : 64 bits
        period is 1/1,000,000,000
        is_steady is 1
    
    high_resolution_clock is the same type as steady_clock
    

    Because of my opening remarks, the "real-life" resolutions are very likely to be identical to xtime's for any given platform.

    Can the C++11 standard clocks cope with durations in the order of days, maybe even weeks on all known platforms?

    Yes. Even months and years.

    The first duration limit you will hit is when dealing with nanosecond resolution. chrono guarantees this will have at least a 64 bit signed integral representation giving you +-292 years of range. When talking about system_clock, this range will be centered on 1970.

    Any other known issues or surprising quirks

    When operating at or near range limits, the chrono library can easily and silently overflow. For example if you compare microseconds::max() with nanoseconds::max(), you will experience an overflow and get an indeterminate result. This happens because the comparison operator will first convert the microseconds to nanoseconds before doing the comparison, and that conversion overflows.

    Steer well clear of the duration and time_point range limits. If you have to deal with them, and are not sure how, look on Stackoverflow for answers. Ask a question specific to your concern if that search is not satisfactory.