base64-2.0: benchmarks/simd-native.c
/* Copyright (c) 2026, Emily Pillmore. BSD-3-Clause; see LICENSE. */
/* Standalone native-prefix timing; no full codec is used in timed regions.
* Build: cc -O2 -std=c99 -Icbits benchmarks/simd-native.c cbits/base64_simd.c -o simd-native-bench
* Every case is validated against independent scalar bytes before timing.
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include "base64_simd.h"
typedef size_t (*prefix_fn)(const uint8_t *, size_t, uint8_t *, const uint8_t *);
struct variant { const char *name; prefix_fn encode; prefix_fn decode; };
static const struct variant variants[] = {
{"native", hs_base64_simd_encode, hs_base64_simd_decode}
};
#define COUNT (sizeof(variants) / sizeof(variants[0]))
static const uint8_t alphabets[2][65] = {
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
};
static volatile size_t sink;
static void require(int condition, const char *message)
{
if (!condition) { fprintf(stderr, "%s\n", message); exit(1); }
}
static double now(void)
{
struct timespec t;
require(clock_gettime(CLOCK_MONOTONIC, &t) == 0, "clock_gettime failed");
return (double)t.tv_sec + (double)t.tv_nsec * 1e-9;
}
static void fixture(uint8_t *p, size_t len, unsigned kind)
{
uint64_t state = UINT64_C(0x243f6a8885a308d3);
for (size_t i = 0; i < len; i++) {
state ^= state << 13; state ^= state >> 7; state ^= state << 17;
p[i] = kind == 0 ? (uint8_t)state : kind == 1 ? (uint8_t)i : 90;
}
}
static void reference_encode(const uint8_t *src, size_t len, uint8_t *dst,
const uint8_t *alphabet)
{
for (size_t i = 0; i + 2 < len; i += 3) {
const uint32_t n = (uint32_t)src[i] << 16 | (uint32_t)src[i + 1] << 8 | src[i + 2];
*dst++ = alphabet[n >> 18]; *dst++ = alphabet[(n >> 12) & 63];
*dst++ = alphabet[(n >> 6) & 63]; *dst++ = alphabet[n & 63];
}
}
static double timed(prefix_fn fn, const uint8_t *src, size_t len, uint8_t *dst,
const uint8_t *table, size_t iterations)
{
size_t total = 0;
const double start = now();
for (size_t i = 0; i < iterations; i++) total += fn(src, len, dst, table);
const double elapsed = now() - start;
sink += total + dst[0];
return elapsed;
}
static size_t calibrate(prefix_fn fn, const uint8_t *src, size_t len, uint8_t *dst,
const uint8_t *table, double target)
{
size_t iterations = 1;
double elapsed;
do {
elapsed = timed(fn, src, len, dst, table, iterations);
if (elapsed < 0.003) iterations *= 2;
} while (elapsed < 0.003);
return (size_t)((double)iterations * target / elapsed) + 1;
}
int main(int argc, char **argv)
{
double seconds = 0.02;
unsigned rounds = 3, timed_alphabet = 0;
const char *only_pattern = NULL;
const size_t sizes[] = {128, 4096, 1048576};
const char *patterns[] = {"random", "cycle256", "repeated"};
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--seconds") == 0 && i + 1 < argc) seconds = strtod(argv[++i], NULL);
else if (strcmp(argv[i], "--rounds") == 0 && i + 1 < argc) rounds = (unsigned)strtoul(argv[++i], NULL, 10);
else if (strcmp(argv[i], "--pattern") == 0 && i + 1 < argc) only_pattern = argv[++i];
else if (strcmp(argv[i], "--url") == 0) timed_alphabet = 1;
else { fprintf(stderr, "usage: %s [--seconds 0.02] [--rounds 3] [--url] [--pattern random|cycle256|repeated]\n", argv[0]); return 1; }
}
require(seconds >= 0.001 && seconds <= 10 && rounds > 0 && rounds <= 50, "invalid timing options");
if (only_pattern) {
int found = 0;
for (size_t i = 0; i < 3; i++) found |= strcmp(only_pattern, patterns[i]) == 0;
require(found, "unknown --pattern (use random, cycle256, or repeated)");
}
const int backend = hs_base64_simd_backend();
fprintf(stderr, "native backend=%d (0 scalar, 1 SSSE3, 2 NEON, 3 AVX2)\n", backend);
require(backend != 0, "no native SIMD backend is available to time");
puts("variant,operation,alphabet,data,raw_bytes,input_bytes,consumed,offset,round,iterations,ns_per_call,input_gib_per_second");
for (unsigned kind = 0; kind < 3; kind++) {
if (only_pattern && strcmp(only_pattern, patterns[kind]) != 0) continue;
for (size_t n = 0; n < sizeof(sizes) / sizeof(sizes[0]); n++) {
const size_t len = sizes[n], encoded_len = len / 3 * 4 + 4;
for (size_t offset = 0; offset <= 1; offset++) {
uint8_t *raw_base = malloc(len + 2), *encoded_base = malloc(encoded_len + 2);
uint8_t *output_base = malloc(encoded_len + 34), *reference = malloc(encoded_len);
require(raw_base && encoded_base && output_base && reference, "allocation failed");
uint8_t *raw = raw_base + offset, *encoded = encoded_base + offset;
uint8_t *output = output_base + offset;
fixture(raw, len, kind);
/* Validate both alphabets even if timing only one. */
for (unsigned alphabet = 0; alphabet < 2; alphabet++) {
uint8_t table[256];
memset(table, 255, sizeof(table));
for (size_t j = 0; j < 64; j++) table[alphabets[alphabet][j]] = (uint8_t)j;
table['='] = 64;
reference_encode(raw, len, encoded, alphabets[alphabet]);
memcpy(reference, encoded, encoded_len - 4);
memcpy(encoded + encoded_len - 4, "AAAA", 4);
for (unsigned operation = 0; operation < 2; operation++) {
const uint8_t *src = operation ? encoded : raw;
const uint8_t *lookup = operation ? table : alphabets[alphabet];
const size_t input_len = operation ? encoded_len : len;
const uint8_t *expected = operation ? raw : reference;
size_t consumed[COUNT] = {0}, iterations[COUNT] = {0};
for (size_t v = 0; v < COUNT; v++) {
prefix_fn fn = operation ? variants[v].decode : variants[v].encode;
memset(output_base, 0xa5, encoded_len + 34);
consumed[v] = fn(src, input_len, output, lookup);
const size_t written = operation ? consumed[v] / 4 * 3 : consumed[v] / 3 * 4;
require(consumed[v] <= input_len && consumed[v] % (operation ? 4 : 3) == 0,
"invalid prefix return");
require(!operation || input_len - consumed[v] >= 4, "final quartet consumed");
require(memcmp(output, expected, written) == 0, "incorrect prefix bytes");
for (size_t j = 0; j < offset; j++) require(output_base[j] == 0xa5, "leading canary changed");
for (size_t j = written; j < encoded_len + 32; j++) require(output[j] == 0xa5, "trailing canary changed");
if (alphabet == timed_alphabet && consumed[v] != 0)
iterations[v] = calibrate(fn, src, input_len, output, lookup, seconds);
}
if (alphabet != timed_alphabet) continue;
for (unsigned round = 0; round < rounds; round++) {
/* Rotate implementation order to limit drift bias. */
for (size_t order = 0; order < COUNT; order++) {
const size_t v = (order + round) % COUNT;
if (consumed[v] == 0) continue;
prefix_fn fn = operation ? variants[v].decode : variants[v].encode;
const double elapsed = timed(fn, src, input_len, output, lookup, iterations[v]);
const double ns = elapsed * 1e9 / iterations[v];
printf("%s,%s,%s,%s,%zu,%zu,%zu,%zu,%u,%zu,%.6f,%.6f\n",
variants[v].name, operation ? "decode" : "encode", alphabet ? "url" : "standard",
patterns[kind], len, input_len, consumed[v], offset, round, iterations[v], ns,
consumed[v] / ns * 1e9 / 1073741824.0);
}
}
}
}
free(reference); free(output_base); free(encoded_base); free(raw_base);
}
}
}
fprintf(stderr, "validated both alphabets; checksum=%zu\n", sink);
return 0;
}