Measuring time in C sounds straightforward until you need precision. time() has one-second resolution. clock() measures CPU time, not wall time. On Windows, the high-resolution timers are different from POSIX. Here is what each function actually does and which one to use for benchmarking.
time() — Wall Clock, One-Second Resolution
#include <stdio.h>
#include <time.h>
int main() {
time_t start = time(NULL);
/* do some work */
for (volatile long i = 0; i < 1000000000L; i++);
time_t end = time(NULL);
printf("Elapsed: %ld secondsn", (long)(end - start));
return 0;
}time() returns seconds since the Unix epoch. Subtract two values to get elapsed seconds. The resolution is 1 second — useless for benchmarking anything faster than that.
clock() — CPU Time, Not Wall Time
#include <stdio.h>
#include <time.h>
int main() {
clock_t start = clock();
for (volatile long i = 0; i < 100000000L; i++);
clock_t end = clock();
double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
printf("CPU time: %.6f secondsn", elapsed);
return 0;
}clock() measures processor time consumed by the program, not wall-clock time. If your program sleeps for 5 seconds, clock() records near zero for that period. This is useful for measuring algorithmic efficiency. It does not include time other processes spend on the CPU.
clock_gettime() — High-Resolution Wall Clock (POSIX)
#include <stdio.h>
#include <time.h>
double elapsed_ms(struct timespec start, struct timespec end) {
return (end.tv_sec - start.tv_sec) * 1000.0
+ (end.tv_nsec - start.tv_nsec) / 1000000.0;
}
int main() {
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
/* work to measure */
for (volatile long i = 0; i < 100000000L; i++);
clock_gettime(CLOCK_MONOTONIC, &t1);
printf("Elapsed: %.3f msn", elapsed_ms(t0, t1));
return 0;
}Use CLOCK_MONOTONIC, not CLOCK_REALTIME. Monotonic clocks never go backward (unlike wall time, which can be adjusted by NTP or the user). Resolution is nanoseconds. This is the right choice for all benchmarking on Linux and macOS.
Clock Types Explained
| Clock ID | What it measures | Use when |
|---|---|---|
CLOCK_REALTIME | Wall clock (can jump) | Timestamps, logging |
CLOCK_MONOTONIC | Always increasing | Benchmarking, timeouts |
CLOCK_PROCESS_CPUTIME_ID | CPU time for this process | CPU profiling |
CLOCK_THREAD_CPUTIME_ID | CPU time for this thread | Per-thread profiling |
A Reusable Benchmark Wrapper
#include <stdio.h>
#include <time.h>
typedef void (*BenchFn)(void);
double benchmark(BenchFn fn, int iterations) {
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
for (int i = 0; i < iterations; i++) fn();
clock_gettime(CLOCK_MONOTONIC, &t1);
double total_ns = (t1.tv_sec - t0.tv_sec) * 1e9
+ (t1.tv_nsec - t0.tv_nsec);
return total_ns / iterations; /* ns per iteration */
}
static volatile long sum = 0;
void work(void) {
for (int i = 0; i < 1000; i++) sum += i;
}
int main() {
double ns = benchmark(work, 10000);
printf("%.2f ns per calln", ns);
return 0;
}Common Benchmarking Mistakes
Not warming up the cache: The first few iterations of a benchmark hit cold caches. Run the function 10–100 times before starting timing to warm instruction and data caches.
Letting the optimizer remove the work: The compiler can eliminate loops whose results are never used. Use volatile on the output, or use the result in a printf after the timing window.
Benchmarking too short an interval: If the operation takes 50 ns and your clock has 100 ns granularity, measure 10,000 iterations and divide. This is exactly what the wrapper above does.
Not compiling with optimization: Benchmarking debug builds is meaningless. Use -O2 when measuring performance. To verify the setup quickly, use our c compiler online before running a local benchmark.
Windows Alternative
#include <windows.h>
#include <stdio.h>
int main() {
LARGE_INTEGER freq, t0, t1;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&t0);
for (volatile long i = 0; i < 100000000L; i++);
QueryPerformanceCounter(&t1);
double ms = (double)(t1.QuadPart - t0.QuadPart) * 1000.0 / freq.QuadPart;
printf("%.3f msn", ms);
return 0;
}On Windows, QueryPerformanceCounter provides nanosecond-range resolution. CLOCK_MONOTONIC from clock_gettime is available in newer Windows SDKs (Visual C++ 2019+) if you prefer portability. See our guide on GCC flags and standards for how to target specific platform behavior.
TL;DR
time()— wall clock, 1-second resolution; fine for coarse timestamps, not benchmarkingclock()— CPU time only; measures work done, not sleep or I/O waitclock_gettime(CLOCK_MONOTONIC)— nanosecond wall clock, never goes backward; the right choice for benchmarking on Linux/macOS- Use
volatileon loop variables or consume the result to prevent the optimizer from eliminating the benchmark - Run multiple iterations and divide to handle timer granularity
- Warm up the cache before timing — first run includes cold-cache misses
- On Windows, use
QueryPerformanceCounterfor high-resolution timing