measurements/randomx-tuning-20260910/README.md
On this page

RandomX tuning: Hetzner and Graviton5

Measured 2026-09-10. The task prioritized hashes per second for one dependent chain on one CPU, not whole-machine mining throughput.

Result and deployment state

Machine Untuned, pinned Tuned, pinned Improvement against current pinned baseline
Hetzner KVM EPYC Genoa VM, vCPU 1 486 H/s 617–619 H/s about 27%
Graviton5 c9g.4xlarge parent, CPU 13 479 H/s 564–569 H/s about 18–19%

These are actual relay dependent-chain calibrations. Earlier untuned solo measurements were 501–512 H/s on Hetzner and 507 H/s on Graviton5; the smaller differences against those earlier runs should also be considered. The Graviton measurements here used a spare parent CPU while the production enclave continued its epoch generation. They are not new measurements inside the enclave.

Hetzner is updated. The existing nine-worker solver controller now selects /opt/attested-relay-fleet-v2/native-490c4b9/relay-timelock through a systemd environment override. Its old binary, configuration, puzzle state, and archives are retained. The idle controller was restarted only after confirming there was no active native solver. The new environment and all three existing services were verified. The controller's fleet size and CPU allocation were not changed to put nine workers on one CPU; CPU 1 affinity applies to these single-core performance tests. See hetzner/tuning-deployment.json.

Graviton's tuned image is built and independently reproduced, but not activated. The existing non-debug enclave production-mullvad-92bd475, ID i-0de9795ee9d3ce090-enc1a0892f694bd5b0, remains running with its original PCRs. Adopting the new allocator requires a new image and restart, which would discard its in-memory warm-up. The user's no-discard instruction is why activation requires an explicit restart decision. No Mullvad device/key was rotated. graviton/production-preserved.json records the unchanged enclave identity.

What changed

On Linux, RandomX's cache, dataset and scratchpad data allocations of at least 2 MiB now receive 2 MiB alignment and madvise(MADV_HUGEPAGE). This makes the kernel's existing madvise policy useful for these allocations, including a single 2 MiB scratchpad. Ordinary backing remains valid if huge pages are unavailable. JIT allocations, W^X transitions, erasure, the RandomX algorithm, and the deployed work count are unchanged.

The source is frozen at 490c4b99abe12f7593ea265f0f25060f1942c737, based on 92bd47508d7ad48442c98148f6b4e09c6169ab5e. The x86 binary was compiled from the base plus the identical allocator and vendored-checksum changes; the frozen commit additionally updates documentation. Its SHA256 is d9ec873e8fc8699d6399972c87af1053246c0c95443af3987f4782a8c7ef9168. The ARM binary/image were built directly from the clean frozen commit.

Affinity and hyperthreading

The final Hetzner check ran:

nice -n 10 taskset -c 1 tuned-relay-timelock calibrate \
  --mode full --samples 50000 --workers 1

Every observed thread's affinity was checked throughout the run and required to equal {1}. The result was 617.09 H/s, with 2,451,570,688 bytes of anonymous huge pages: the complete 256 MiB cache, 2080 MiB dataset, and 2 MiB scratchpad. See hetzner/single-core-verified/result.json for all observations.

Guest topology reports CPU 1's thread-sibling list as 1, smt/active=0, and smt/control=notsupported. Thus the benchmark is restricted to one exposed vCPU; the physical host's SMT topology and sibling occupancy remain unknown. Affinity does not establish that another VM cannot occupy a physical sibling. There is no guest control that can establish physical exclusivity on this VM.

Initial experiments deliberately included unpinned comparisons, retained in the raw matrix. The quoted before/after table uses pinned runs. After the user asked for single-core pinning, the queued nine-process throughput test was canceled before starting, and all subsequent performance measurements used CPU 1.

Tested settings

The initial Hetzner tuned matrix's process-name filter missed its renamed executable, so its three peak_anon_huge_bytes: 0 fields are missing telemetry, not evidence of zero huge-page use. Timing results are unaffected. The final single-core check corrected the monitoring and verified both affinity and full page coverage. The original measurements are retained, together with an extra tuned-smaps-*.json snapshot. Early Hetzner matrix cases also overlapped a low-priority build/test on CPUs 14–15; the final affinity-verified run did not.

Validation and reproducibility

Public candidate downloads include both EIFs, the report, Sigstore bundle, and scanned frozen source. Anonymous report and bundle downloads matched their locally verified bytes. The candidate is explicitly a prerelease; the existing production release remains marked latest. Verification artifacts are retained in ci/.

The candidate PCR0 is:

170c028642b96c828ce0bf83415615008e180cc44fd5dbf9e3f5fea24ed18921b093a1a7d6b2e94d46be38c1908c4688

The candidate has not replaced the current production PCR0. Build provenance does not constitute a fresh runtime attestation.

Timing implications

The work count remains 306,376,868 serial hashes per puzzle. At the measured Hetzner single-chain rates, that projects about 5.73–5.75 days; at the tuned Graviton parent rates, about 6.23–6.28 days. These estimates exclude operational overhead and are not minimum attacker recovery times. Keeping the work count fixed while speeding up the solver shortens its delay. A new seven-worker generation benchmark inside Nitro remains necessary to establish its tuned generation time; multiplying a solo improvement into the old generation result would only be an estimate.

Sources: Linux THP documentation, AWS Graviton performance guidance, XMRig huge-page guidance, XMRig MSR settings.