Files

163 lines
6.4 KiB
C

/*
* 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 <stddef.h>/<string.h> (guard
* VLIBC_STDDEF_H) conflicts with the system <stdio.h>/<time.h> (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 <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <time.h>
#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;
}