/* * Benchmark harness for the five L1 string functions — strlen, strcmp, * memcpy, memmove, memset (musts/BENCHMARKING.md). * * Deliberately a SYSTEM-headers-only translation unit: it includes no vlibc * header, because vlibc's self-contained / (guard * VLIBC_STDDEF_H) conflicts with the system / (guard * _STDDEF_H) — both define size_t/NULL/offsetof/max_align_t and mixing them * hard-errors. Which libc implements the timed calls is therefore decided * purely at link time (../libvlibc.la vs the system libc). */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #define ITERATIONS 50000000ULL /* * Volatile function pointers defeat both GCC builtin substitution and * pure-attribute LICM hoisting of the timed loops (-fno-builtin alone does * not prevent the latter). The signatures match the system-header * declarations exactly: memcpy has restrict, memmove does not. */ static size_t (*volatile p_strlen)(const char *) = strlen; static int (*volatile p_strcmp)(const char *, const char *) = strcmp; static void *(*volatile p_memcpy)(void *restrict, const void *restrict, size_t) = memcpy; static void *(*volatile p_memmove)(void *, const void *, size_t) = memmove; static void *(*volatile p_memset)(void *, int, size_t) = memset; /* * XOR accumulator. Every timed result is folded in here so the compiler * cannot discard the indirect calls; volatile keeps the store itself alive. */ static volatile unsigned long long sink; /* Fixed small inputs: 8-byte and 64-byte buffers, sized per the plan. */ static char str8[8] = "abcdefg"; /* 7 non-NUL bytes + NUL: full scan */ static char str64[64] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789."; static char cmp8a[8] = "abcdefg"; static char cmp8b[8] = "abcdefg"; /* equal: strcmp runs the full length */ static char cmp64a[64] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789."; static char cmp64b[64] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789."; static char src8[8]; static char src64[64]; static char dst8[8]; static char dst64[64]; /* * Time one expression executed ITERATIONS times through a volatile function * pointer, then print `name@size ns/call`. The result of each call is XORed * into a local accumulator and folded into `sink` after the loop, so the * calls cannot be eliminated. On clock_gettime failure, returns 1 out of * main. */ #define MEASURE(name, size, call) \ do \ { \ unsigned long long r = 0; \ double seconds; \ if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) \ { \ return 1; \ } \ for (unsigned long long i = 0; i < ITERATIONS; i++) \ { \ r ^= (unsigned long long)(call); \ } \ if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) \ { \ return 1; \ } \ sink ^= r; \ seconds = (double)(end.tv_sec - start.tv_sec) + \ (double)(end.tv_nsec - start.tv_nsec) / 1000000000.0; \ printf("%s@%d %.1f\n", name, size, \ seconds * 1000000000.0 / (double)ITERATIONS); \ } while (0) int main(void) { struct timespec start; struct timespec end; /* * Correctness self-asserts, routed through the SAME volatile function * pointers as the timed loops. A bare literal call (strlen("hello")) * would be constant-folded by GCC even at -O0, and a call through a * non-volatile pointer would not exercise the pointer the timing uses. */ { char scratch[16]; if (p_strlen("hello") != 5) { return 1; } if (p_strcmp("a", "b") >= 0) { return 1; } if (p_strcmp("abc", "abc") != 0) { return 1; } if (p_memset(scratch, 0, sizeof scratch) != scratch) { return 1; } if (p_memcpy(scratch, "hello", 6) != scratch) { return 1; } if (p_strcmp(scratch, "hello") != 0) { return 1; } if (p_memmove(scratch + 1, scratch, 6) != scratch + 1) { return 1; } if (p_strcmp(scratch + 1, "hello") != 0) { return 1; } } MEASURE("strlen", 8, p_strlen(str8)); MEASURE("strlen", 64, p_strlen(str64)); MEASURE("strcmp", 8, p_strcmp(cmp8a, cmp8b)); MEASURE("strcmp", 64, p_strcmp(cmp64a, cmp64b)); MEASURE("memcpy", 8, p_memcpy(dst8, src8, sizeof dst8)); MEASURE("memcpy", 64, p_memcpy(dst64, src64, sizeof dst64)); MEASURE("memmove", 8, p_memmove(dst8, src8, sizeof dst8)); MEASURE("memmove", 64, p_memmove(dst64, src64, sizeof dst64)); MEASURE("memset", 8, p_memset(dst8, 0x5a, sizeof dst8)); MEASURE("memset", 64, p_memset(dst64, 0x5a, sizeof dst64)); /* Keep the accumulated results observable to the compiler. */ if (sink == 0xdeadbeefULL) { printf("%llu\n", sink); } /* * Explicit flush: this harness is a host program, but under * --with-libc=vlibc its DT_NEEDED order puts libvlibc.so before * libc.so.6, so the exit() that runs at process end is vlibc's — which * does not flush stdio yet (that hook lands with the stdio todo). * Without the flush the timed results above are lost. */ fflush(stdout); return 0; }