13

The following snippet of code:

struct timespec ts;
for (int x = 0; x < 100000000; x++) {
    timespec_get(&ts, TIME_UTC);
    long cTime = (long) time(NULL);
    if (cTime != ts.tv_sec && ts.tv_nsec < 3000000) {
        printf("cTime: %ld\n", cTime);
        printf("ts.tv_sec: %ld\n", ts.tv_sec);
        printf("ts.tv_nsec: %ld\n", ts.tv_nsec);
    }
}

produces this output:

...
cTime: 1579268059
ts.tv_sec: 1579268060
ts.tv_nsec: 2527419
cTime: 1579268059
ts.tv_sec: 1579268060
ts.tv_nsec: 2534036
cTime: 1579268059
ts.tv_sec: 1579268060
ts.tv_nsec: 2540359
cTime: 1579268059
ts.tv_sec: 1579268060
ts.tv_nsec: 2547039
...

Why the discrepancy between cTime and ts.tv_sec? Note that the problem does not occur if the conditional is changed to ts.tv_nsec >= 3000000. The problem relies on nanoseconds being smaller than 3000000.

Theo d'Or
  • 783
  • 1
  • 4
  • 17
  • You need to be more specific about the operating system used, version of it, version of the C library used. – Some programmer dude Jan 17 '20 at 14:01
  • 2
    @Someprogrammerdude Linux Debian 8, GCC 6.3.0. – Theo d'Or Jan 17 '20 at 14:09
  • What's `timespec_get()`? Is this C or C++? Looks like `std::timespec_get`. Please use the appropriate tag. – Marco Bonelli Jan 17 '20 at 14:24
  • @MarcoBonelli: It was added to C in C11. [Can reproduce online](https://tio.run/##VZFNb8IwDIbv/RUeEywVHXTHrbBLtQMa0MPKbVJVJS5ElKRqUsaE@O1d0owvH5rGefL6jU2f15S27SMXtGwYwkRpxuVo8@5dU5rv0GY8LjTsci7IXnLme0cPTChdN1SDhVSFFLQKQKNoFEzhGAbwEpo4RR1byBqIVTmYwzAyy8Sd2zDb4dAHp2rjLJmtUZOB1U1ni49slcZ@dIFKKdZAU4MaSWJ3vr1Hlqv5/AbjBRBHPUyNxZHeZwrpbTUbVW3MFaTXkW/QL9m36AVO/kbsFtWq40b98PWfvqiff4WtdH/7AMOpe3kE4zEkgiL8IChE0BsExhWtscoF/Q1M/olBybfuqJSyAi2h4IKrDSgpxZ0yLSXdZiIXUpWIFYnnSfyZLZJlkibLWRyAGcmgm08ArkfWwJdljSEwY8s15F3vbZncjhrwQFEpvkf3bG5azpq6W65OocKaS3Zxc/Lc99S2fw "C (gcc) – Try It Online"). – ShadowRanger Jan 17 '20 at 14:32
  • @ShadowRanger thank you for the reference, I couldn't see a `man` entry for `timespec_get` on my system so I jumped to conclusions. Makes sense. – Marco Bonelli Jan 17 '20 at 14:35

1 Answers1

12

The reason is, that you (implicitly) use different system clocks. timespec_get() uses the high resolution system-wide realtime clock, while time() uses the coarse realtime clock.

Try to use

clock_gettime(CLOCK_REALTIME_COARSE, &ts);

instead of your timespec_get(), then the difference should vanish.

Edit:

This can be seen in the Linux Kernel Source, vclock_gettime.c

Indeed the issue is a bit subtle to see here. The seconds-part of the structure members used by CLOCK_REALTIME_COARSE and CLOCK_REALTIME contain identical values, but the nanoseconds-part is different; with CLOCK_REALTIME it can be larger than 1000000000 (which is one second). In this case, it is fixed up on the call:

ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
ts->tv_nsec = ns;

This correction is neither performed with CLOCK_REALTIME_COARSE, nor with time(). This explains the difference between CLOCK_REALTIME and time().

Ctx
  • 18,090
  • 24
  • 36
  • 51
  • Is this documented anywhere, or just an artifact of `time` being implemented with the (presumably) more performant but less accurate clock (on the theory that it's only got second granularity anyway, so who needs precision)? Lagging real time by a millisecond or so (online tests showed occasional lag of over a ms, but not much over) when you're only asking for second granularity isn't all that important I guess. – ShadowRanger Jan 17 '20 at 14:33
  • @ShadowRanger I added some more details – Ctx Jan 17 '20 at 15:06
  • Not explicit documentation of intent, but that's enough detail for an up-vote. :-) Funny that the clock can actually report more than a second's worth of additional nanoseconds. – ShadowRanger Jan 17 '20 at 15:09
  • @ShadowRanger I could not find a real documentation other than the source for that, which also means, that the behaviour might also change in detail without prior notice – Ctx Jan 17 '20 at 15:12
  • @Ctx Thank you for the detailed answer! I will use timespec_get() rather than clock_gettime() that you advise, as timespec_get() is C11 rather than POSIX and doesn't require the setting of which clock to use. I had no idea different clocks were used, but given the choice, I don't see much point in using the coarse clock. – Theo d'Or Jan 17 '20 at 15:47