keccak-fast.c

Minimal SIMD keccak implementation
git clone git://git.finwo.net/lib/keccak-fast.c
Log | Files | Refs | README | LICENSE

commit 84898a129c15d87587135e8c245742617b0d0e7b
parent 51be4c74a70bfdcfa108768dde7ff034d782d387
Author: finwo <finwo@pm.me>
Date:   Sat, 10 Oct 2026 17:28:25 +0200

Bench all backends

Diffstat:
MREADME.md | 2+-
Mbench/bench.c | 145+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/backend/avx2.c | 2+-
Msrc/backend/avx512.c | 2+-
Msrc/backend/scalar_bmi.c | 2+-
Msrc/keccak-fast-internal.h | 13+++++++++++++
6 files changed, 100 insertions(+), 66 deletions(-)

diff --git a/README.md b/README.md @@ -54,7 +54,7 @@ also gets `rorx` for the rotates. About 20% faster than the donor. ``` make test # KAT and batch differential (TAP) -make bench # vendored donor, pinned core, best-of-nine ns/hash +make bench # per-hash table: scalar, scalar+bmi, avx2, avx512 (+ donor) ``` `bench/` fetches the donor via `dep install`. `OPT=-O2|-O3` (arch flags diff --git a/bench/bench.c b/bench/bench.c @@ -7,6 +7,7 @@ #include <time.h> #include "keccak-fast.h" +#include "keccak-fast-internal.h" int kct_sha3_256(uint8_t *, size_t, const uint8_t *, size_t); int kct_shake256(uint8_t *, size_t, const uint8_t *, size_t); @@ -51,24 +52,10 @@ static size_t pick_iters(kf_fn fn, uint8_t *out, size_t outlen, return iters < 100 ? 100 : iters; } -static void run_pair(const char *name, kf_fn donor, kf_fn fast, size_t inlen, - size_t outlen, uint8_t *in, uint8_t *out, int reps, - uint64_t target_ns) { - size_t iters = pick_iters(donor, out, outlen, in, inlen, target_ns); - double bt = -1.0; - double bf = -1.0; - for (int r = 0; r < reps; r++) { - double t = time_once(donor, out, outlen, in, inlen, iters); - double f = time_once(fast, out, outlen, in, inlen, iters); - if (bt < 0.0 || t < bt) bt = t; - if (bf < 0.0 || f < bf) bf = f; - } - printf("%s\n", name); - printf(" %-8s %9.2f ns/hash %9.3f Mh/s\n", "donor", bt, 1000.0 / bt); - printf(" %-8s %9.2f ns/hash %9.3f Mh/s\n", kf_backend_name(), bf, - 1000.0 / bf); - printf(" %-8s %9.3fx\n\n", "fast/do", bf / bt); -} +typedef struct { + const char *label; + kf_fn fn; +} oneshot_t; static double time_batch(kf_batch_fn fn, size_t count, const uint8_t *in, size_t inlen, uint8_t *out, size_t outlen, @@ -81,36 +68,70 @@ static double time_batch(kf_batch_fn fn, size_t count, const uint8_t *in, return (double)(now_ns() - t0) / ((double)iters * (double)count); } -static void case_batch(const char *name, kf_batch_fn fn, size_t count, - size_t inlen, size_t outlen, uint8_t *in, uint8_t *out, - int reps, uint64_t target_ns) { - double per = time_batch(fn, count, in, inlen, out, outlen, 200); - if (per <= 0.0) per = 1.0; - size_t iters = (size_t)((double)target_ns / (per * (double)count)); - if (iters < 10) iters = 10; +typedef struct { + const char *label; + kf_batch_perm_fn perm; + int lanes; +} batch_t; + +static void case_all(const char *title, const oneshot_t *os, int nos, + const batch_t *bs, int nbs, kf_fn donor, + kf_batch_fn batch_fn, size_t count, size_t inlen, + size_t outlen, uint8_t *in, uint8_t *out, int reps, + uint64_t target_ns) { + double bd = -1.0, bos[4], bbs[4]; + size_t iters = pick_iters(donor, out, outlen, in, inlen, target_ns); + for (int i = 0; i < nos; i++) bos[i] = -1.0; + for (int i = 0; i < nbs; i++) bbs[i] = -1.0; + + size_t biters = 0; + if (nbs > 0) { + kf_batch_perm = bs[0].perm; + kf_batch_lanes = bs[0].lanes; + double per = time_batch(batch_fn, count, in, inlen, out, outlen, 200); + if (per <= 0.0) per = 1.0; + biters = (size_t)((double)target_ns / (per * (double)count)); + if (biters < 10) biters = 10; + } - double best = -1.0; for (int r = 0; r < reps; r++) { - double v = time_batch(fn, count, in, inlen, out, outlen, iters); - if (best < 0.0 || v < best) best = v; + double d = time_once(donor, out, outlen, in, inlen, iters); + if (bd < 0.0 || d < bd) bd = d; + for (int i = 0; i < nos; i++) { + double v = time_once(os[i].fn, out, outlen, in, inlen, iters); + if (bos[i] < 0.0 || v < bos[i]) bos[i] = v; + } + for (int i = 0; i < nbs; i++) { + kf_batch_perm = bs[i].perm; + kf_batch_lanes = bs[i].lanes; + double v = time_batch(batch_fn, count, in, inlen, out, outlen, biters); + if (bbs[i] < 0.0 || v < bbs[i]) bbs[i] = v; + } } - char label[64]; - snprintf(label, sizeof label, "%s count %zu", name, count); - printf("%-24s %9.2f ns/hash %9.3f Mh/s\n", label, best, 1000.0 / best); + + printf("%s\n", title); + printf(" %-10s %9.2f ns/hash %9.3f Mh/s\n", "donor", bd, 1000.0 / bd); + for (int i = 0; i < nos; i++) + printf(" %-10s %9.2f ns/hash %9.3f Mh/s\n", os[i].label, bos[i], + 1000.0 / bos[i]); + for (int i = 0; i < nbs; i++) + printf(" %-10s %9.2f ns/hash %9.3f Mh/s\n", bs[i].label, bbs[i], + 1000.0 / bbs[i]); + printf("\n"); } int main(void) { const int reps = 9; const uint64_t target_ns = 25ULL * 1000000ULL; - uint8_t in[256]; - static uint8_t out[4096]; + static uint8_t in[64 * 1088]; + static uint8_t out[64 * 1088]; int cpu = 0; const char *cs = getenv("BENCH_CPU"); for (size_t i = 0; i < sizeof in; i++) in[i] = (uint8_t)(i * 31 + 7); if (cs) cpu = atoi(cs); - printf("keccak-fast vs coruus/keccak-tiny\n"); + printf("keccak-fast backend comparison\n"); printf("build: %s\n", BENCH_FLAGS); if (pin_cpu(cpu) != 0) { printf("# warning: could not pin to cpu %d, results will be noisier\n", cpu); @@ -120,34 +141,34 @@ int main(void) { printf("best of %d interleaved reps, about %llu ms per rep\n\n", reps, (unsigned long long)(target_ns / 1000000ULL)); - run_pair("SHA3-256 32->32", kct_sha3_256, kf_sha3_256, 32, 32, in, out, reps, - target_ns); - run_pair("SHA3-256 64->32", kct_sha3_256, kf_sha3_256, 64, 32, in, out, reps, - target_ns); - run_pair("SHAKE256 32->32", kct_shake256, kf_shake256, 32, 32, in, out, reps, - target_ns); - run_pair("SHAKE256 32->1088", kct_shake256, kf_shake256, 32, 1088, in, out, reps, - target_ns); - - static uint8_t bin[256 * 64]; - static uint8_t bout[256 * 64]; - for (size_t i = 0; i < sizeof bin; i++) bin[i] = (uint8_t)(i * 17 + 3); - - printf("batch SHAKE256 32->32, per hash (%s)\n", kf_batch_name()); - case_batch(kf_batch_name(), kf_shake256_batch, 8, 32, 32, bin, bout, reps, - target_ns); - case_batch(kf_batch_name(), kf_shake256_batch, 64, 32, 32, bin, bout, reps, - target_ns); - case_batch(kf_batch_name(), kf_shake256_batch, 256, 32, 32, bin, bout, reps, - target_ns); - - printf("batch SHA3-256 32->32, per hash (%s)\n", kf_batch_name()); - case_batch(kf_batch_name(), kf_sha3_256_batch, 8, 32, 32, bin, bout, reps, - target_ns); - case_batch(kf_batch_name(), kf_sha3_256_batch, 64, 32, 32, bin, bout, reps, - target_ns); - case_batch(kf_batch_name(), kf_sha3_256_batch, 256, 32, 32, bin, bout, reps, - target_ns); + const oneshot_t sha3[] = { + {"scalar", kf_scalar_sha3_256}, + {kf_backend_name(), kf_sha3_256}, + }; + const oneshot_t shake[] = { + {"scalar", kf_scalar_shake256}, + {kf_backend_name(), kf_shake256}, + }; + + batch_t batch[4]; + int nb = 0; +#if (defined(__x86_64__) || defined(_M_X64) || defined(__i386__)) && \ + !defined(KECCAK_FAST_NO_AVX2) + batch[nb++] = (batch_t){"avx2 x4", kf_avx2_batch, 4}; +#endif +#if (defined(__x86_64__) || defined(_M_X64) || defined(__i386__)) && \ + !defined(KECCAK_FAST_NO_AVX512) + batch[nb++] = (batch_t){"avx512 x8", kf_avx512_batch, 8}; +#endif + + case_all("SHA3-256 32->32, per hash", sha3, 2, batch, nb, kct_sha3_256, + kf_sha3_256_batch, 64, 32, 32, in, out, reps, target_ns); + case_all("SHA3-256 64->32, per hash", sha3, 2, batch, nb, kct_sha3_256, + kf_sha3_256_batch, 64, 64, 32, in, out, reps, target_ns); + case_all("SHAKE256 32->32, per hash", shake, 2, batch, nb, kct_shake256, + kf_shake256_batch, 64, 32, 32, in, out, reps, target_ns); + case_all("SHAKE256 32->1088, per hash", shake, 2, batch, nb, kct_shake256, + kf_shake256_batch, 64, 32, 1088, in, out, reps, target_ns); printf("sink %llu\n", (unsigned long long)g_sink); return 0; diff --git a/src/backend/avx2.c b/src/backend/avx2.c @@ -100,7 +100,7 @@ static void kf_permute_x4(uint64_t s[4][25]) { } __attribute__((target("avx2"))) -static void kf_avx2_batch(uint64_t states[][25], size_t count) { +void kf_avx2_batch(uint64_t states[][25], size_t count) { size_t i = 0; for (; i + 4 <= count; i += 4) { kf_permute_x4(&states[i]); diff --git a/src/backend/avx512.c b/src/backend/avx512.c @@ -96,7 +96,7 @@ static void kf_permute_x8(uint64_t s[8][25]) { } __attribute__((target("avx512f"))) -static void kf_avx512_batch(uint64_t states[][25], size_t count) { +void kf_avx512_batch(uint64_t states[][25], size_t count) { size_t i = 0; for (; i + 8 <= count; i += 8) { kf_permute_x8(&states[i]); diff --git a/src/backend/scalar_bmi.c b/src/backend/scalar_bmi.c @@ -22,7 +22,7 @@ #define KF_LINKAGE static #include "scalar-impl.h" -static void kf_bmi_batch(uint64_t states[][25], size_t count) { +void kf_bmi_batch(uint64_t states[][25], size_t count) { for (size_t i = 0; i < count; i++) { keccakf(states[i]); } diff --git a/src/keccak-fast-internal.h b/src/keccak-fast-internal.h @@ -42,4 +42,17 @@ int kf_scalar_turboshake256(uint8_t *out, size_t outlen, const uint8_t *in, void kf_scalar_permute(uint64_t state[25]); void kf_scalar_batch(uint64_t states[][25], size_t count); +#if (defined(__x86_64__) || defined(_M_X64) || defined(__i386__)) && \ + !defined(KECCAK_FAST_NO_BMI) +void kf_bmi_batch(uint64_t states[][25], size_t count); +#endif +#if (defined(__x86_64__) || defined(_M_X64) || defined(__i386__)) && \ + !defined(KECCAK_FAST_NO_AVX2) +void kf_avx2_batch(uint64_t states[][25], size_t count); +#endif +#if (defined(__x86_64__) || defined(_M_X64) || defined(__i386__)) && \ + !defined(KECCAK_FAST_NO_AVX512) +void kf_avx512_batch(uint64_t states[][25], size_t count); +#endif + #endif