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
- Both kernels initially used THP
madvise. Changing it temporarily toalwaysgave Hetzner 584–594 H/s, and Graviton 495–511 H/s. The Graviton runs had only partial huge-page coverage. Explicit alignment/advice gave better results without a globalalwayspolicy. - Tuned single-chain runs used 50,000 hashes, repeated at pinned and unpinned affinity. Initial untuned/global-policy runs used 20,000 hashes. Dataset initialization is separate from reported hashing time. Dataset keys vary between calibrations, and these are short measurements rather than sustained recovery tests.
- A same-library, single-thread independent-input check compared THP with an explicitly reserved 2 MiB huge-page pool. Hetzner measured 617.321 versus 618.925 H/s; Graviton measured 570.674 versus 574.273 H/s. Each pair produced identical output hashes. These sub-1% differences do not establish a useful advantage beyond normal short-run variation. All temporary reservations were restored; Graviton's original 24 one-GiB Nitro pages were preserved.
- Neither machine exposes a guest CPU-frequency governor. On Hetzner, the four inspected tuning MSRs read zero. A write/readback test of the first documented Zen4 preset register remained zero, showing that setting was ineffective. The original register and MSR-write policy were restored. No effective MSR tuning is claimed. Physical BIOS, power limits, memory timings and host SMT cannot be controlled from these guests.
- No security mitigations or native hardening were disabled. No new cloud machine was provisioned and no existing files or directories were deleted.
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
- x86 Rust native unit tests passed, including real generation/solve/decrypt, restart/tampering checks, entropy behavior and upstream vectors. The explicit full-dataset vector passed.
- x86 AddressSanitizer/UndefinedBehaviorSanitizer native checks passed in light and full modes, with secure-JIT/interpreter agreement, pre-release erasure observations and five injected page-permission failures.
- The actual ARM release library passed the equivalent light/full native checks, including erasure and fail-closed page permissions. These ARM checks were not sanitizer-instrumented.
- GitHub run 34440508901
passed two fresh ARM builds and the separate comparison/signing job. The
report and an actual EIF verified locally against the Sigstore bundle with
exact CI revision
8b7ad95e880cd851822188d88712b377cc00ab9apinned and self-hosted runners denied.
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.