measurements/multiply-chain-20260910/README.md
On this page

Dependent integer multiplication and wider-state experiments

Measured 2026-09-10 UTC on the existing M4 laptop, Graviton5 parent, and Hetzner EPYC Genoa guest. Graviton5 has a reproducible advantage for the tested nonlinear low-half multiply-add chains, including wider mixed states. It does not win every multiplication workload: the M4 wins multiply-high, and this Hetzner machine wins the explicitly SIMD 32-bit implementation.

These are instruction-mix experiments, not cryptographic constructions or time-lock security bounds. Small state, chosen constants, and lack of cryptanalysis make these unsuitable as a replacement for RandomX. Nonlinear mixing prevents the most obvious affine recurrence simplification; it does not prove the absence of shortcuts, ASIC advantages, cycles, or attacks.

Final measurements

Nanoseconds per whole dependent round, median of three runs of one billion rounds each; smaller is faster. One software thread throughout. A four-lane round updates all four lanes and mixes neighboring results; its displayed rate is not the sum of four independent chains.

Same workload, best tested implementation Graviton5 M4 Hetzner Genoa Graviton speedup over M4 / Hetzner
64-bit low multiply-add, rotate, XOR 0.911 1.388 1.656 1.52x / 1.82x
64-bit multiply-high, rotate, add, XOR 1.552 1.151 1.655 0.74x / 1.07x
Four 64-bit multiply-add lanes, then mix 1.217 1.677 1.940 1.38x / 1.59x
Four 32-bit multiply-add lanes, then mix 1.217 1.638 1.928 1.35x / 1.58x

For the last row, scalar code wins on both ARM machines; explicit SIMD wins on Hetzner. The explicitly SIMD-only results are 3.416 / 2.407 / 1.928 ns for Graviton / M4 / Hetzner. Merely widening to SIMD made this serial ARM workload slower. Four 64-bit lanes use scalar integer multiplication, not four SIMD 64-bit multiplies: ordinary NEON has no direct 64-bit lane multiplication.

The low-half Graviton result is about 1.10 billion rounds/second on one core, versus 720 million on M4 and 604 million on Hetzner. These very small rounds must not be compared numerically with RandomX hashes, SHA digests, or delay security. They contain radically different amounts and kinds of work.

summary.json retains medians, ranges, exact output checksums, and winning binary names for every variant, including alternatives omitted from this table. M4 timing varies more than the servers: its ordinary low-chain final samples span 1.407โ€“1.575 ns. The table uses the optimized equivalent variant, whose samples span 1.375โ€“1.466 ns. These differences are short-run estimates rather than guaranteed sustained device limits.

Semantics and optimization

Arithmetic is unsigned modulo the word size. Constants are C = 0xd1342543de82ef95, D = 0x9e3779b97f4a7c15; 32-bit code truncates them. All single-state chains start at 1; all four-lane states start [1,2,3,4].

Each new round consumes the preceding round's state. Adjacent-lane mixing propagates influence through the full state over several rounds; this is not claimed to be full cryptographic diffusion in a single round.

For each host we tested loop unroll factors 1, 4, and 16, both normal compiler optimization and auto-vectorization disabled. Each configuration ran twice at 100 million rounds in the final sweep. The fastest mean configuration for each source variant was then measured independently three times at one billion rounds. The table selects the faster median when source variants have identical semantics. This is a finite optimization search, not a proof of optimal machine code. The chosen binaries and generated assembly are retained.

Native ARM assembly uses MADD and UMULH. The x86 code uses IMUL/MUL, and the 32-bit vector implementation uses VPMULLD, VPADDD, VPSHUFD, VPROLD, and VPXOR (the exposed AVX-512 support is used). Disabling automatic vectorization helps avoid unfavorable SIMD choices for some scalar formulations. The explicit vector type remains vectorized in these control builds.

Apple Clang originally emits MADD, then a standalone rotate, then XOR with the shifted previous state. GCC on Graviton shifts the old state alongside MADD and folds the rotate into EOR. The lowopt ARM inline-assembly variant explicitly implements the latter schedule on both ARM machines. It gives no large M4 improvement and preserves Graviton's advantage. On x86 lowopt calls the same low-chain function; small differences are code placement or run noise.

The low-chain data dependency in the inspected Graviton assembly is MADD -> EOR-with-rotate; the old state's LSR can execute alongside MADD. The analogous x86 path is IMUL -> ADD -> RORX -> XOR, with the old-state shift independent of the multiply path. This difference provides a concrete explanation for the measured advantage. These are assembly dependency paths, not a separate measurement of individual instruction latency. The explicit ARM schedule is:

madd t, x, C, D
lsr  old, x, #23
eor  x, old, t, ror #47

Accumulator dependency control

A deliberately affine diagnostic repeats x = a*b+x for constant operands. It is trivially shortcuttable and excluded from nonlinear results above. The first acc version obscures the multiplier operands from the optimizer, but unrolling can still reassociate additions. This produced misleadingly low Graviton times (~0.168 ns), so it is not evidence of that fast a dependent accumulator instruction.

accforced.c additionally makes the accumulator opaque at each iteration, preventing compiler reassociation while retaining the actual hardware dependency. Unroll-16, three one-billion-step runs give:

Forced accumulator dependency Graviton5 M4 Hetzner
ns/update 0.304 0.235 0.277

This is a roughly one-cycle accumulator path on Graviton at 3.3 GHz. An x86 core can overlap the independent multiplies and serialize only the adds, so fused MADD does not by itself create an advantage in this specific recurrence. These control results match the exact final checksum of the original diagnostic.

Correctness, environment, and reproduction

run.py independently computes every workload in Python for 1,024 rounds and checks each binary before timing it. It also checks equal final checksums for all implementations of the same workload. summarize.py verifies that all one-billion-round final checksums agree across all three platforms, including the scalar/SIMD and low/lowopt equivalents and accumulator controls. Source files transferred to the servers match the saved local files byte for byte. The four-lane return value is a folded checksum of the final state.

Other agents' benchmark and build workloads were paused for the final sweeps and confirmation runs. This does not eliminate preexisting production load, Mac applications, hypervisor effects, or unknown physical co-tenants. Different compilers mean these compare the optimized available implementations, not compiler-controlled isolated microarchitecture performance.

Compile bench.c with the flags above to bench-u1, bench-u4, bench-u16, and bench-novec-u1, bench-novec-u4, bench-novec-u16. Then run, using the appropriate host name and CPU (empty string on macOS):

python3 run.py local-sweep '' 100000000
python3 final.py local

For Linux, substitute hetz-sweep 2 / hetz 2 or graviton-sweep 14 / graviton 14. Compile accforced.c with the corresponding native flags and -DUNROLL=16, then run accforced acc 1000000000 three times with the same pin. summarize.py regenerates the combined summary from the archived host layouts. Raw stdout, parsed results, environment metadata, assembly, source, and executables are retained under this directory. SHA256SUMS covers the files.

The practical lead is low-half integer multiplication with fused addition and dependent bit mixing, not multiplication generally or SIMD generally. Whether incorporating this mix into RandomX changes its platform balance or security requires a separate design and implementation study.