Solarian Programmer

My programming ramblings

C17 Programming - measuring execution time, delaying program execution

Posted on April 17, 2019 by Paul

This is a short note about measuring the execution time for a C program, or delaying the program execution for a specified amount of time. I’m only interested in using mechanisms that let us measure the execution time with at least millisecond resolution, so I won’t mention the classical C way of getting the time of day with second resolution time or using the clock function.

You can find all the code examples at the GitHub repo for this article.

Since C11, the standard way to get a good time estimate is to use the timespec_get function, which returns the system clock time:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <time.h>
 4 
 5 int main(void) {
 6     struct timespec t0, t1;
 7 
 8     if(timespec_get(&t0, TIME_UTC) != TIME_UTC) {
 9         printf("Error in calling timespec_get\n");
10         exit(EXIT_FAILURE);
11     }
12 
13     // Do some work ...
14 
15     if(timespec_get(&t1, TIME_UTC) != TIME_UTC) {
16         printf("Error in calling timespec_get\n");
17         exit(EXIT_FAILURE);
18     }
19 
20     // Calculate the elapsed time
21     double diff = (double)(t1.tv_sec - t0.tv_sec) + ((double)(t1.tv_nsec - t0.tv_nsec)/1000000000L);
22     printf("Elapsed time: %lf seconds\n", diff);
23 }

Where timspec will store the time split in seconds and nanoseconds.

Unfortunately, at the time of this writing, you can not use the timespec_get function on macOS. It works on Linux and Windows.

On Posix systems, like Linux and macOS, we can use the clock_gettime function to get an accurate time value. We can rewrite the above program to work on most recent versions of Linux, macOS and BSDs like this:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <time.h>
 4 
 5 int main(void) {
 6     struct timespec t0, t1;
 7 
 8     if(clock_gettime(CLOCK_REALTIME, &t0) != 0) {
 9         perror("Error in calling clock_gettime");
10         exit(EXIT_FAILURE);
11     }
12 
13     // Do some work ...
14 
15     if(clock_gettime(CLOCK_REALTIME, &t1) != 0) {
16         perror("Error in calling clock_gettime");
17         exit(EXIT_FAILURE);
18     }
19 
20     // Calculate the elapsed time
21     double diff = (double)(t1.tv_sec - t0.tv_sec) + ((double)(t1.tv_nsec - t0.tv_nsec)/1000000000L);
22     printf("Elapsed time: %lf seconds\n", diff);
23 }

As a side note, timespec_get, on Posix systems, when available, corresponds to calling clock_gettime with a CLOCK_REALTIME argument.

Another observation is that clock_gettime is not available on Windows.

Let’s implement a portable version of timespec_get:

 1 #include "time_utils.h"
 2 
 3 bool gettime(struct timespec *ts) {
 4 #ifdef _WIN32
 5     if(timespec_get(ts, TIME_UTC) == TIME_UTC) {
 6         return true;
 7     }
 8 #else
 9     if(clock_gettime(CLOCK_REALTIME, ts) == 0) {
10         return true;
11     }
12 #endif
13     return false;
14 }

where time_utils.h contains:

1 #pragma once
2 
3 #include <time.h>
4 #include <stdbool.h>
5 
6 // Portable timspec_get equivalent, returns bool true on success
7 bool gettime(struct timespec *ts);

With the above time_utils.c and time_utils.h we can rewrite our initial example in a portable way:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include "time_utils.h"
 4 
 5 int main(void) {
 6     struct timespec t0, t1;
 7 
 8     if(!gettime(&t0)) {
 9         printf("Error in calling gettime\n");
10         exit(EXIT_FAILURE);
11     }
12 
13     // Do some work ...
14 
15     if(!gettime(&t1)) {
16         printf("Error in calling gettime\n");
17         exit(EXIT_FAILURE);
18     }
19 
20     // Calculate the elapsed time
21     double diff = (double)(t1.tv_sec - t0.tv_sec) + ((double)(t1.tv_nsec - t0.tv_nsec)/1000000000L);
22     printf("Elapsed time: %lf seconds\n", diff);
23 }

Assuming that the above program was saved as gettime_usage_demo.c, this is how you can build it on Linux, macOS or Windows:

gcc -std=gnu17 -Wall -Wextra time_utils.c gettime_usage_demo.c -o gettime_usage_demo

clang -std=c17 -Wall -Wextra -pedantic time_utils.c gettime_usage_demo.c -o gettime_usage_demo

cl /W4 time_utils.c gettime_usage_demo.c /Fe:gettime_usage_demo.exe

How about delaying program execution for a specified amount of time ? If your C compiler supports C11 threads.h functionality you can use the thrd_sleep function. Unfortunately you won’t be able to use threads.h on Windows or macOS.

Here is an example of modifying our last code to wait for half a second using thrd_sleep, this works only on Linux:

 1 // This works only on Linux
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include "time_utils.h"
 5 #include <threads.h>
 6 
 7 int main(void) {
 8     struct timespec t0, t1;
 9 
10     if(!gettime(&t0)) {
11         printf("Error in calling gettime\n");
12         exit(EXIT_FAILURE);
13     }
14 
15     // Delay program execution for half a second
16     const struct timespec req = {.tv_nsec = 500000000L};
17     thrd_sleep(&req, NULL);
18 
19     if(!gettime(&t1)) {
20         printf("Error in calling gettime\n");
21         exit(EXIT_FAILURE);
22     }
23 
24     // Calculate the elapsed time
25     double diff = (double)(t1.tv_sec - t0.tv_sec) + ((double)(t1.tv_nsec - t0.tv_nsec)/1000000000L);
26     printf("Elapsed time: %lf seconds\n", diff);
27 }

Assuming that you’ve saved the above program as thrd_sleep_demo.c, this is what I see if I build and run the code on a Linux machine:

~ $ gcc -std=gnu17 -Wall -Wextra time_utils.c thrd_sleep_demo.c -o thrd_sleep_demo
~ $ ./thrd_sleep_demo
Elapsed time: 0.501247 seconds

On Posix systems you can also use the nanosleep function, this has the advantage that it will work on both Linux and macOS. Here is the above code modified to work on Posix systems:

 1 // This works on Posix systems (e.g. macOS, Linux)
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include "time_utils.h"
 5 
 6 int main(void) {
 7     struct timespec t0, t1;
 8 
 9     if(!gettime(&t0)) {
10         printf("Error in calling gettime\n");
11         exit(EXIT_FAILURE);
12     }
13 
14     // Delay program execution for half a second
15     const struct timespec req = {.tv_nsec = 500000000L};
16     if(nanosleep(&req, NULL) != 0) {
17         perror("Error in calling clock_gettime");
18         exit(EXIT_FAILURE);
19     }
20 
21     if(!gettime(&t1)) {
22         printf("Error in calling gettime\n");
23         exit(EXIT_FAILURE);
24     }
25 
26     // Calculate the elapsed time
27     double diff = (double)(t1.tv_sec - t0.tv_sec) + ((double)(t1.tv_nsec - t0.tv_nsec)/1000000000L);
28     printf("Elapsed time: %lf seconds\n", diff);
29 }

Assuming that you’ve saved the above program as nanosleep_demo.c, this is what I see if I build and run the code on a macOS machine:

~ $ clang -std=c17 -Wall -Wextra -pedantic time_utils.c nanosleep_demo.c -o nanosleep_demo
~ $ ./nanosleep_demo
Elapsed time: 0.504071 seconds

As mentioned before, we can’t use the nanosleep function on Windows. A Windows replacement for Posix’s nansleep is the Sleep function. Please note that the Sleep function from Windows accepts as a single argument the time duration in milliseconds of the requested delay. Also, the argument to the Windows Sleep function is of type DWORD which is equivalent to uint32_t.

Using Sleep from Windows and nanosleep from Posix systems, we can write a portable delay function (we’ll put the new function in time_utils.c):

 1 #ifdef _WIN32
 2 #include <windows.h>
 3 #endif
 4 
 5 #include "time_utils.h"
 6 
 7 bool gettime(struct timespec *ts) {
 8     // ...
 9 }
10 
11 bool delay(uint32_t tms) {
12 #ifdef _WIN32
13     Sleep(tms);
14     return true;
15 #else
16     time_t tv_sec = tms / 1000;
17     long tv_nsec = (tms % 1000) * 1000000;
18     const struct timespec req = {.tv_sec = tv_sec, .tv_nsec = tv_nsec};
19     if(nanosleep(&req, NULL) == 0) {
20         return true;
21     }
22     return false;
23 #endif
24 }

We’ll also need to modify time_utils.h:

 1 #include <time.h>
 2 #include <stdbool.h>
 3 #include <stdint.h>
 4 
 5 // Portable timspec_get equivalent, returns bool true on success
 6 bool gettime(struct timespec *ts);
 7 
 8 // Portable sleep function.
 9 // input: time in milliseconds from 0 to 4294967295
10 // output: bool true on success or false on failure
11 // on Windows the function will always returns true since Sleep has no return value
12 bool delay(uint32_t tms);

Here is an example of using the delay function from above to wait for 1.5 seconds:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include "time_utils.h"
 4 
 5 int main(void) {
 6     struct timespec t0, t1;
 7 
 8     if(!gettime(&t0)) {
 9         printf("Error in calling gettime\n");
10         exit(EXIT_FAILURE);
11     }
12 
13     // Delay program execution for one and a half seconds
14     if(!delay(1500)) {
15         printf("Error in calling delay\n");
16         exit(EXIT_FAILURE);
17     }
18 
19     if(!gettime(&t1)) {
20         printf("Error in calling gettime\n");
21         exit(EXIT_FAILURE);
22     }
23 
24     // Calculate the elapsed time
25     double diff = (double)(t1.tv_sec - t0.tv_sec) + ((double)(t1.tv_nsec - t0.tv_nsec)/1000000000L);
26     printf("Elapsed time: %lf seconds\n", diff);
27 }

If I build and run the code on a Linux machine, this is what I see:

~ $ gcc -std=gnu17 -Wall -Wextra time_utils.c delay_demo.c -o delay_demo
~ $ ./delay_demo
Elapsed time: 1.500561 seconds

or, on a Windows machine:

C:\DEV>cl /W4 time_utils.c delay_demo.c /Fe:delay_demo.exe
C:\DEV>delay_demo
Elapsed time: 1.503510 seconds

If you want to learn more about C99/C11 I would recommend reading 21st Century C: C Tips from the New School by Ben Klemens:

If you want to learn more about Linux or Posix like systems, I can recommend The Linux Programming Interface by M. Kerrisk:


Show Comments