Programming with Nanoseconds

From last week’s Lesson, the clock_gettime() function returns values in both time_t (Unix Epoch) seconds as well as nanoseconds. This rich variety makes the function quite useful for coding time-critical details where values less than a second are desired.

I recall programming for ancient microcomputers where various system functions returned the system time accurate to values less than one second. These details made it easy to code wait times less than a second, which I often did for communications programs. (I did a lot of “modem” programming back in the day.)

When I began programming for Unix, I noticed that the standard C library offered functions that only tracked time to the second — nothing faster. The clock_gettime() function, and its capability of returning nanosecond values, piqued my curiosity. Specifically, I wanted to know how fast a program could fetch the nanosecond values.

The following code calls the clock_gettime() function twice, each time capturing the nanosecond value. The output shows the difference between the values or the number of nanoseconds that passes between function calls.

2023_05_13-Lesson-a.c

#include <stdio.h>
#include <time.h>

int main()
{
    struct timespec res;
    long nano1,nano2;

    /* read consecutive nanosecond values */
    clock_gettime(CLOCK_REALTIME,&res);
    nano1 = res.tv_nsec;
    clock_gettime(CLOCK_REALTIME,&res);
    nano2 = res.tv_nsec;
    printf("Difference: %lu\n",nano2-nano1);

    return(0);
}

Variable res.tv_nsec respresents the nanosecond member of the timespec structure, the number of nanoseconds elapsed since the last second returned from a clock_gettime() function call. At Line 11, the initial value fetched is saved in variable nano1. Then the code calls the clock_gettime() function again, this time storing the res.tv_nsec value in variable nano2. A printf() statement outputs the difference.

Here’s a sample run:

Difference: 88

In the output, 88 nanoseconds passed between clock_gettime() calls. I ran the code a few times and the values ranged from 69 to 93 nanoseconds. Changing the CLOCK_REALTIME constant to CLOCK_MONOTONIC increased the range of values returned from 54 to 126 nanoseconds. Don’t ask me to explain why, though the larger range did surprise me.

The clock_gettime() function takes time itself to do its thing, which means trying to pare down the nanosecond interval between calls is going to be difficult. Aside from coding the call in Assembly (which may not show any improvements), I modified the code to use two timespec structures. This way I could call the clock_gettime() function twice in a row, hopefully to obtain a tighter nanosecond interval. Here is the code:

2023_05_13-Lesson-b.c

#include <stdio.h>
#include <time.h>

int main()
{
    struct timespec res1,res2;

    /* read consecutive nanosecond values */
    clock_gettime(CLOCK_REALTIME,&res1);
    clock_gettime(CLOCK_REALTIME,&res2);
    printf("Difference: %lu\n",res2.tv_nsec-res1.tv_nsec);

    return(0);
}

Alas, this improvement didn’t noticeably alter the output:

Difference: 70

Multiple runs showed intervals similar to the first program. Regardless, my goal was to see how tight I could make the calls. After all, if you want to write a program that steps in nanosecond increments, would it be possible to use the clock_gettime() function to either set the intervals or time them? Probably not.

In next week’s Lesson, I use the clock_gettime() function to write a time-delay function using the nanosecond values. Well, using them as best I can.

4 thoughts on “Programming with Nanoseconds

  1. I ran 2023_05_13-LESSON-B on my system—an Intel Core i7 11900H (“Tiger Lake”) 8-core mobile CPU laptop—several times as well, and also noted a difference between CLOCK_MONOTONIC (usually reporting a »nano2-nano1« difference of around 25) and CLOCK_REALTIME (usually reporting a difference in the low thirties). I too donʼt know why that is or should be.

    That notwithstanding I thought that it might be interesting to turn things around by relying on sleep() to wait for 1 second, and then display the times clock_gettime() is reporting:

    clock_gettime (CLOCK_REALTIME, &start);
    sleep (1);
    clock_gettime (CLOCK_REALTIME, &end);

    Hereʼre the results from my system (Ubuntu 22.04.1 x64):

    CLOCK_MONOTONIC reports a resolution (precision) of 1 ns.
    end-start for CLOCK_REALTIME == 1000276760ns
    end-start for CLOCK_MONOTONIC == 1000198585ns

    For completeness, I then added my previously coded read_tsc()¹ function (to read the CPUʼs timestamp counter) as well:

    get_approx_tsc_freq() = 2500000000 Hz
    end-start for read_tsc(): 998656694ns

    Now, I havenʼt investigated this further, but looking at the variance these values exhibit on repeated runs, I think that μs² precision is probably the best one can hope to achieve on todayʼs preemptive multitasking OS.

    That being said, if I had to wait for really short periods of time, Iʼd (in a tight loop) probably prefer GCCʼs __rdtscp() intrinsic to calls to clock_gettime().

    As always, I have uploaded project files for Codelite (Linux) as well as Visual Studio (Windows) on GitHub (should anyone be interested): https://tinyurl.com/5fwvt5sk

    ¹ Hang On a Sec, Part II: https://c-for-dummies.com/blog/?p=5805#comment-449
    ² QueryPerformanceFrequency()—a Win32 function comparable to clock_getres()—actually reports a somewhat more believable precision of 100ns on my system…

  2. You always post such great stuff! Thank you.

    One issue I didn’t think of until later – and which I cover in a future Lesson – is how to deal with the nanosecond rollover. It’s something I never considered until I was wracking my brain about the simple math in this Lesson’s code.

  3. Glad you find my contributions interesting!

    With regards to “nanosecond rollover” I agree—itʼs fine to ignore this possibility in simple examples, but definitely should be handled in real code.

    BTW, I just noticed that the TLS certificate for this site is set to expire on Sat, 13 May 2023 14:45:23 GMT! (Just to let you know, in case there isnʼt a crontab entry for automatic renewal.)

Leave a Reply