base16-2.0: benchmarks/simd-native.c
/* Copyright (c) 2026, Emily Pillmore. BSD-3-Clause; see LICENSE. */
/* Native diagnostic benchmark; this does not measure Haskell/FFI overhead. */
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "base16_simd.h"
static volatile size_t sink;
static const uint8_t alphabet[] = "0123456789abcdef";
static double now(void)
{
struct timespec time;
if (clock_gettime(CLOCK_MONOTONIC, &time) != 0) {
perror("clock_gettime");
exit(EXIT_FAILURE);
}
return (double)time.tv_sec + (double)time.tv_nsec / 1000000000.0;
}
static void encode(const uint8_t *src, size_t len, uint8_t *dst, int simd)
{
size_t consumed = simd ? hs_base16_simd_encode(src, len, dst) : 0;
for (size_t i = consumed; i < len; i++) {
dst[i * 2] = alphabet[src[i] >> 4];
dst[i * 2 + 1] = alphabet[src[i] & 15];
}
}
static uint8_t nibble(uint8_t value)
{
return value <= '9' ? (uint8_t)(value - '0') : (uint8_t)(value - 'a' + 10);
}
/* The benchmark only supplies independently generated valid lowercase input. */
static void decode(const uint8_t *src, size_t len, uint8_t *dst, int simd)
{
size_t consumed = simd ? hs_base16_simd_decode(src, len, dst) : 0;
for (size_t i = consumed; i < len; i += 2) {
dst[i / 2] = (uint8_t)(nibble(src[i]) * 16 + nibble(src[i + 1]));
}
}
typedef void (*operation)(const uint8_t *, size_t, uint8_t *, int);
static double measure(operation op, const uint8_t *src, size_t len,
uint8_t *dst, int simd, double seconds)
{
const double start = now();
double elapsed;
size_t iterations = 0;
do {
for (size_t batch = 0; batch < 16; batch++) {
op(src, len, dst, simd);
sink += dst[0];
iterations++;
}
elapsed = now() - start;
} while (elapsed < seconds);
return (double)iterations * (double)len / elapsed / 1048576.0;
}
static void usage(const char *name)
{
fprintf(stderr, "usage: %s [--seconds 0.001..10] [--rounds 1..20] "
"[--pattern random|repeating]\n", name);
}
int main(int argc, char **argv)
{
double seconds = 0.05;
unsigned long rounds = 3;
int random_pattern = 1;
const size_t sizes[] = {64, 128, 1024, 16384, 1048576};
const int backend = hs_base16_simd_backend();
for (int i = 1; i < argc; i += 2) {
char *end;
if (i + 1 >= argc) {
usage(argv[0]);
return EXIT_FAILURE;
}
errno = 0;
if (strcmp(argv[i], "--seconds") == 0) {
seconds = strtod(argv[i + 1], &end);
if (errno != 0 || end == argv[i + 1] || *end != '\0' ||
!isfinite(seconds) || seconds < 0.001 || seconds > 10) {
usage(argv[0]);
return EXIT_FAILURE;
}
} else if (strcmp(argv[i], "--rounds") == 0) {
rounds = strtoul(argv[i + 1], &end, 10);
if (errno != 0 || end == argv[i + 1] || *end != '\0' ||
rounds < 1 || rounds > 20) {
usage(argv[0]);
return EXIT_FAILURE;
}
} else if (strcmp(argv[i], "--pattern") == 0 &&
(strcmp(argv[i + 1], "random") == 0 ||
strcmp(argv[i + 1], "repeating") == 0)) {
random_pattern = strcmp(argv[i + 1], "random") == 0;
} else {
usage(argv[0]);
return EXIT_FAILURE;
}
}
printf("SIMD backend: %d (%s); input throughput MiB/s; pattern=%s\n", backend,
backend == 3 ? "AVX2" : backend == 2 ? "NEON" :
backend == 1 ? "SSSE3" : "scalar fallback",
random_pattern ? "random" : "repeating");
puts("bytes operation scalar simd");
for (size_t sample = 0; sample < sizeof(sizes) / sizeof(sizes[0]); sample++) {
const size_t len = sizes[sample];
uint8_t *src = malloc(len);
uint8_t *encoded = malloc(len * 2);
uint8_t *output = malloc(len * 2);
uint32_t state = UINT32_C(0x6d253fb9);
if (src == NULL || encoded == NULL || output == NULL) {
fputs("allocation failed\n", stderr);
free(src);
free(encoded);
free(output);
return EXIT_FAILURE;
}
for (size_t i = 0; i < len; i++) {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
src[i] = random_pattern ? (uint8_t)state : 0xfb;
}
encode(src, len, encoded, 0);
encode(src, len, output, 1);
if (memcmp(encoded, output, len * 2) != 0) {
fputs("SIMD encoding differs from scalar reference\n", stderr);
return EXIT_FAILURE;
}
decode(encoded, len * 2, output, 1);
if (memcmp(src, output, len) != 0) {
fputs("SIMD decoding differs from scalar reference\n", stderr);
return EXIT_FAILURE;
}
for (unsigned long round = 0; round < rounds; round++) {
const double scalar_encode = measure(encode, src, len, output, 0, seconds);
const double simd_encode = measure(encode, src, len, output, 1, seconds);
const double scalar_decode = measure(decode, encoded, len * 2, output, 0, seconds);
const double simd_decode = measure(decode, encoded, len * 2, output, 1, seconds);
printf("%zu encode %.1f %.1f\n", len, scalar_encode, simd_encode);
printf("%zu decode %.1f %.1f\n", len * 2, scalar_decode, simd_decode);
}
free(output);
free(encoded);
free(src);
}
return EXIT_SUCCESS;
}