Sequential SHA3-256 optimization experiment
Measured 2026-09-10 UTC. Graviton5 reaches 6.813 million sequential hashes/s, versus 4.056 million on Hetzner and 8.072 million on the M4. These are the best variant medians, each from three 10-million-hash samples. Graviton is 1.68x the measured Genoa VM, while the M4 is 1.18x Graviton.
| Implementation | M4, million H/s | Graviton5, million H/s | Hetzner, million H/s |
|---|---|---|---|
| Scalar assembly + C wrapper | 7.791 | 5.925 | 3.594 |
| ARM SHA3 / x86 AVX512 + C wrapper | 7.507 | 6.365 | 3.096 |
| x86 AVX2 + C wrapper | — | — | 3.304 |
| x86 AVX512VL + C wrapper | — | — | 3.632 |
| Fused chain in assembly | 7.875 | 6.808 | 4.056 |
| Fused, all 24 rounds unrolled | 8.072 | 6.813 | 4.049 |
The Graviton SHA3 instructions improve the equivalent wrapped scalar code by about 7%; keeping the chain in registers brings the total improvement to 15%. The x86 fused wrapper improves its best wrapped vector variant by about 12%. Full unrolling has effectively no benefit on Graviton or x86 at this precision. The M4 unrolled variant is the most consistent of its variants (8.065–8.088M); other Mac measurements vary more, so small differences should not be overread. This is a useful Graviton advantage over the measured x86 VM, but not an order-of-magnitude gap and not an advantage over the M4.
All 42 full runs have the same 10-million-step final digest. summary.json
retains sample values, medians, means, ranges, and the common digest. Binaries,
disassemblies, compiler versions, sources, and raw measurements are retained.
Disassembly confirms the ARM crypto instructions and x86 ternary-logic/rotate
instructions are present in the intended implementations.
This experiment measures x = SHA3-256(x) starting from 32 zero bytes. Every
iteration consumes the preceding iteration's complete 32-byte digest. Each
hash contains all 24 Keccak-f[1600] rounds and standard SHA3 domain separation
and padding. No independently progressing messages are counted as one chain.
Implementations
All machines use assembly generated from the same OpenSSL 3.5.0 source tag,
retained in upstream/ with its Apache 2.0 license. The code calls the primitive
directly instead of repeatedly constructing an EVP context. This is a benchmark
of internal assembly routines, not an application recommendation to depend on
OpenSSL's private ABI.
scalar: OpenSSL scalar integer assembly, C loop resetting the state and preparing the fixed padded 32-byte message.- ARM
simd: OpenSSL ARM SHA3 instructions (EOR3,RAX1,XAR,BCAX), same C wrapper and exact computation. This is explicit selection, not a capability-mask override; it provides the scalar comparison directly. - x86
simd: OpenSSL AVX-512 implementation; additionallyavx2andavx512vltest OpenSSL's other vector implementations on the same chain. AVX512VL here primarily uses 256-bit YMM registers and AVX-512 rotate/ternary-logic features. fused: custom assembly wrapper retains the digest in registers, resets only the rest of the state to the fixed padding, and calls the OpenSSL permutation with register inputs. ABI save/restore happens once per chain. ARM uses the crypto extension permutation; x86 uses AVX512VL after exploring all variants.unrolled: same fused wrapper, but with the complete 24-round permutation expanded in code, preserving every round and round constant. This tests whether removing the inner loop helps. It does not reduce the round count.
generate.py retains the upstream round bodies and mechanically produces the
fused and unrolled variants. On x86, OpenSSL uses a rearranged internal lane
layout; the custom wrapper places the final padding bit in that layout. Only
the initial four lanes are returned as the SHA3-256 digest.
Reproduction
Run LC_ALL=C CC=cc python3 generate.py from this directory (or pass its path).
Generators are retained so an additional source download is unnecessary.
M4:
clang -O3 -mcpu=apple-m4 -I upstream bench.c keccak-m4.S -o bench-m4
python3 run.py m4 none bench-m4
Hetzner:
gcc -O3 -march=znver4 -Wl,-z,noexecstack bench.c keccak-x86-scalar.S keccak-x86-simd.S keccak-x86-avx2.S keccak-x86-avx512vl.S -o bench-gcc
python3 run.py hetzner 1 bench-gcc
Graviton5: compile in the existing attested-relay-arm-toolchain:20260909
container, with this directory bound at /bench, then execute on the host:
cc -O3 -mcpu=native -I /bench/upstream /bench/bench.c /bench/keccak-graviton5.S -o /bench/bench-gcc
python3 run.py graviton5 13 bench-gcc
The existing Graviton compiler did not recognize -mcpu=neoverse-v3;
-mcpu=native compiled successfully. The hashing hot path is explicit assembly.
No production code, enclave configuration, or host settings were changed.
Named build containers remain stopped, and all experiment files are preserved.
Correctness and measurement limits
Every invocation checks the standard SHA3-256("abc") vector with scalar and
accelerated code, then checks that all compiled variants produce an identical
1,000-step chain. The Python runner independently verifies that digest using
hashlib.sha3_256. The final measured 10-million-step digests are also compared
across implementations and machines. Setup/selftests occur outside the timer.
Final runs use three samples of ten million dependent hashes per variant, alternating variant order. Linux uses one thread pinned to CPU 13 on Graviton and guest vCPU 1 on Hetzner. The Mac is one thread, scheduled by macOS, without hard core affinity. The final measurement window is coordinated with other benchmark agents; ordinary background activity and production workloads remain. Hetzner physical host contention and physical-core exclusivity are unknown. Short measurements do not establish sustained multi-day performance or maxima.
This is ordinary SHA3-256, not RandomX and not an evaluated replacement for its memory-hard delay construction. Optimizing a dependent hash chain does not establish a lower bound for adversaries with different hardware or ASICs.