What Does Entering the Kernel Cost? A System Call vs the vDSO
On this virtual server, how much slower is reading the monotonic clock through a real system call than through the vDSO, and what does the cheapest possible system call cost?
Results
| Repeats | 5 |
|---|---|
| Iterations | 2000000 |
| Syscall Getppid Ns | 508.4 |
| Vdso Speedup Factor | 21.76 |
| Vdso Clock Gettime Ns | 27.6 |
| Syscall Clock Gettime Ns | 599.5 |
Recorded October 7, 2026 at 2:21 PM UTC, wall clock 11.9s.
Method
The same request, clock_gettime(CLOCK_MONOTONIC), is made two ways: through the C library, which resolves it in the vDSO without entering the kernel, and through syscall(SYS_clock_gettime, ...), which forces a real kernel entry for an identical answer. getppid() through syscall() is measured as a third case because the kernel does almost no work for it, so its cost is close to the price of the user-to-kernel round trip alone. Each case runs 2,000,000 calls in a tight loop with the result folded into a volatile variable so the compiler cannot remove the calls. All three are warmed up at a tenth of the workload, then measured five times in rotation, and the fastest run of each is reported in nanoseconds per call. The kernel's speculative-execution mitigations are left as the host ships them; their cost is part of what a system call costs here.
Machine
| CPU | AMD EPYC 9354P 32-Core Processor |
|---|---|
| Cores visible | 8 |
| Memory | 31.3 GB |
| Kernel | 6.8.0-139-generic |
| Architecture | x64 |
| Compiler | gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 |
This is a shared virtual server, not an isolated test rig. Absolute throughput will differ on your hardware; the ratio between the two cases is the part that carries over.
Source
The complete program that produced the numbers above. Nothing else was running under our control during the measurement.
/*
* Measures what crossing into the kernel costs, by running the same operation
* with and without the crossing.
*
* clock_gettime(CLOCK_MONOTONIC) is normally answered by the vDSO: code the
* kernel maps into every process that reads the clock from shared memory
* without entering the kernel. syscall(SYS_clock_gettime, ...) forces the real
* system call for the identical request. getppid() is added as the cheapest
* useful syscall there is, so the figure is mostly entry and exit.
*
* Build: gcc -O2 -o bench bench.c
* Run: ./bench <iterations>
* Output: one JSON object on stdout.
*/
#define _GNU_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
static double now_seconds(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
}
/* Results are folded into a volatile sink so the compiler cannot drop calls. */
static volatile long sink;
static double run_vdso(long n) {
struct timespec ts;
double start = now_seconds();
for (long i = 0; i < n; i++) {
clock_gettime(CLOCK_MONOTONIC, &ts);
sink += ts.tv_nsec;
}
return (now_seconds() - start) / (double)n * 1e9;
}
static double run_syscall_clock(long n) {
struct timespec ts;
double start = now_seconds();
for (long i = 0; i < n; i++) {
syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &ts);
sink += ts.tv_nsec;
}
return (now_seconds() - start) / (double)n * 1e9;
}
static double run_getppid(long n) {
double start = now_seconds();
for (long i = 0; i < n; i++) sink += syscall(SYS_getppid);
return (now_seconds() - start) / (double)n * 1e9;
}
int main(int argc, char **argv) {
long n = argc > 1 ? atol(argv[1]) : 10000000L;
run_vdso(n / 10); /* warm-up */
run_syscall_clock(n / 10);
run_getppid(n / 10);
const int repeats = 5;
double vdso = 1e18, sys_clock = 1e18, ppid = 1e18;
for (int r = 0; r < repeats; r++) {
double a = run_vdso(n), b = run_syscall_clock(n), c = run_getppid(n);
if (a < vdso) vdso = a;
if (b < sys_clock) sys_clock = b;
if (c < ppid) ppid = c;
}
printf("{\n");
printf(" \"iterations\": %ld,\n", n);
printf(" \"repeats\": %d,\n", repeats);
printf(" \"vdso_clock_gettime_ns\": %.1f,\n", vdso);
printf(" \"syscall_clock_gettime_ns\": %.1f,\n", sys_clock);
printf(" \"syscall_getppid_ns\": %.1f,\n", ppid);
printf(" \"vdso_speedup_factor\": %.2f\n", sys_clock / vdso);
printf("}\n");
return 0;
}