2

The following program:

#include <chrono>
#include <iostream>
#include <vector>

inline uint64_t now() {
    return std::chrono::duration_cast
       <std::chrono::nanoseconds> 
       (std::chrono::system_clock::now()
          .time_since_epoch())
       .count();
}

int main() {
        std::vector<uint64_t> v;
        for (int i = 0; i < 1000; i++)
                v.push_back(now());

        for (int i = 0; i < v.size()-1; i++)
                std::cout << v[i+1] - v[i] << std::endl;
}

prints numbers in the range of about 250 to 300 on:

g++ (Ubuntu 8.2.0-7ubuntu1) 8.2.0

with:

Linux 4.18.0-15-generic #16-Ubuntu SMP x86_64 x86_64 x86_64 GNU/Linux

Meaning std::chrono::system_clock is nanosecond precision on this system (most likely gettimeofday right?). I have several questions:

  1. Whats the difference on this system between std::chrono::system_clock and std::chrono::steady_clock? (Yes, I know they are specified differently in the standard, I am taking about this implementation.)

  2. Is the answer the same across all libstdc++ targets?

  3. Is the answer the same across all libc++ targets?

  4. Is the answer the same on Windows/MSVC targets?

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319

1 Answers1

4

I'm not sure you are asking the questions you want answered. One thing I see is you asking about difference between steady and system clocks, in terms of their precision. The second, judging from the snippet alone, is about the performance of the system_clock::now, duration_cast, vector::push_back/vector::insert and (implicit) vector::resize.

I'll try to answer the first of those two, if you don't mind:

  • the crux of those clocks is that one (system_clock) is good for interop with any physical calendar and therefore can sometimes go back (with the summer/winter time transition when someone or something changes the system time on the machine, see Difference between std::system_clock and std::steady_clock?), while the other (steady_clock) is guaranteed to only go forward and is good for e.g. measuring how long push_back is.
  • there are no guarantees about the resolution of those clocks. That's why you should keep the clock's duration type as long as reasonable and only use .count() accessor just before printing; but, since there are NO guarantees about the period used, you should probably either
    1. do a duration_cast to something stable,
    2. or perform some fancy suffix selection, using the periods as arguments to some metaprograms.
  • there are no guarantees about the meaning of time_since_epoch() and prior to C++20 there is no way of comparing time_points/durations belonging to two different clocks
  • and, please remember, there are NO guarantees about the resolution of the period for any clock, on any system; I found out the hard way (writing some fancy templates), that there is even no guarantee the period will be divisible by 1000... For one of the clocks, one of the libraries used 1 over 10^8 as the period...

So, asking about any particular implementation and hoping their constants will be also used in other implementations -- even for the same vendor -- is not advisable. I'd always try to either use clock's::time_point, or its ::duration, or, as a last resort, milliseconds or nanoseconds, depending on what do I measure and how fast the measured thingines can fly.

And also please note there are system_clock::(to/from)_time_t() functions, which definitely will produce a 1 over 1 value (seconds), even if the system_clock::duration has a finer period.

The revised snippet, using steady_clock, its time_point and calling duration_cast as late as possible would be:

#include <chrono>
#include <iostream>
#include <vector>

int main() {
        using namespace std::chrono;
        using clock = steady_clock;

        std::vector<clock::time_point> v;
        for (int i = 0; i < 1000; i++)
                v.push_back(clock::now());

        for (size_t i = 0; i < v.size()-1; i++) {
                std::cout
                        << duration_cast<nanoseconds>(
                                v[i+1] - v[i]
                                ).count()
                        << "ns\n";
        }
}

Edit: Oh, and another thing is there is nothing in the original code that would prove your library uses nano as a period in the system_clock. You are doing a duration_cast<nanoseconds> (which uses integer division if it must) and getting the period from that, but with different duration, something like duration_cast<duration<long long, pico>>, you could also get the nonzero somewhere below the lowest 1000. Not likely, but possible never the less.

Edit 2: Sheesh this is complicated. Changed the reason for system_clock being unsteady in the first bullet point.

Marcin Zdun
  • 142
  • 1
  • 8
  • Are you saying that the `now()` function in my code can go backwards? Doesn't it return nanoseconds since UTC epoch, which is uneffected by (for example) day light savings time? Under what circumstances could it go backwards? – Andrew Tomazos Feb 18 '19 at 09:28
  • Yes, it can go backwards, but (probably) not for reasons I gave. When user can change the clock in the system, the system_clock will reflect that and go to whatever new time_point is now current, but the steady_clock will still return some other source of time (probably the uptime, but this also is not defined, at least not in C++17). Updated the answer to remove the timezone-hopping and linking another answer with the better explanation. – Marcin Zdun Feb 18 '19 at 10:01