measurements/randomx-jit-profile-20260910/README.md
On this page

RandomX JIT phase profiling and native compiler targeting

This study instruments the existing RandomX v2 implementation to separate program generation/initialization, writable-page transition, machine-code compilation, executable-page transition, and execution of the generated program. It also compares normal release compilation with CMake ARCH=native. The comparable variants retain hardware AES, full-memory mode, secure JIT transitions, and the previous transparent-huge-page allocation advice. A Mac native-targeting configuration trap is retained separately below. Production source and services were not modified.

Results

Throughput is total hashes divided by total timed seconds across two 12,000- hash samples; setup is excluded. These are fixed-key, 68-byte dependent-chain probes, not the earlier production CLI calibration with random keys.

Machine Default targeting Native/explicit targeting Outcome
Graviton5 571.56 H/s 570.89 H/s No useful gain
M4 741.83 H/s 729.84 H/s, explicit M4 + hardware AES No useful gain
Hetzner 617.34 H/s 607.81 H/s No useful gain

The M4 explicit-target samples span 720.5–739.4 H/s. Short-run scheduling and frequency variation remain uncontrolled; these small regressions do not prove native targeting is inherently slower. They provide no evidence for adopting it as an optimization. No production compilation setting was changed.

Measured percentage of total hashing time in the instrumented builds:

Phase Graviton5 M4 Hetzner
Generate program/initialize registers 0.12% 0.10% 0.13%
Make JIT memory writable 1.19% 0.02% 2.56%
Compile generated program 1.77% 2.92% 3.34%
Make JIT memory executable 1.13% 3.17% 1.76%
Execute generated program 90.63% 87.09% 87.11%

The remainder is work outside these phases. Graviton's code generation and permission transitions account for about 4.1% combined, so eliminating only those costs cannot explain or recover a large performance gap. Improving the emitted program's execution or memory behavior would be a separate optimization project; the profile does not establish that it has no room for improvement.

All twenty final samples (three variants on three machines, plus corrected M4 targeting) produce the same 12,000-hash digest. Instrumentation records the expected 192,000 generated-program executions per two-sample process. Default versus native uses the same algorithm and deterministic inputs. The source's existing hardened primitives were preserved. This is cross-implementation agreement, not an additional independent cryptographic audit.

Mac native-targeting trap

This version of RandomX CMake uses -march=native for ARM ARCH=native. On the installed Apple Clang, this option does not define __ARM_FEATURE_CRYPTO; -mcpu=apple-m4 does. RandomX therefore reports flags 156 instead of 158, selecting software AES. Its 323.17 H/s result is retained in the raw data but excluded from the comparable table.

m4-explicit.py creates another isolated source copy and changes just the ARM native flag to -mcpu=apple-m4; its exact CMake patch is saved. This restores flags 158 and hardware AES without changing the algorithm. Two additional 12,000-hash samples verify the same digest. The production/default build was already using hardware AES and did not have this configuration problem.

Method

prepare.py copies the current vendored source into a separate local experiment directory, adds the saved instrumentation.patch, and writes source-instrumented.tar.gz. The snapshot includes existing uncommitted native hardening changes. This is an experiment source snapshot, not a clean-commit or independently attested build.

build.py compiles three static libraries and the same small C++ harness:

The fixed public dataset key is relay-jit-profile-fixed-key-v1. The harness hashes a 68-byte message containing a fixed prefix, zero metadata, little-endian iteration number, and the immediately preceding complete 32-byte digest. This is a deterministic probe of the same dependent RandomX primitive/input length; it is not the production CLI calibration or its randomly chosen dataset keys. All three hosts and all variants must produce identical final digests.

Cache/dataset initialization happens once per process and is excluded from hashing time. There is one thread throughout, including initialization. Linux final runs pin CPU 13 on Graviton and vCPU 1 on Hetzner; the Mac is scheduled by macOS. Linux huge-page settings were not changed. The existing production Nitro enclave continues on its existing reserved CPUs.

The initial 8,000-hash profile files were collected during other agents' exploration and are labeled preliminary. Final files use an exclusive study window after all other agents' benchmark/build jobs finish, with two samples of 12,000 hashes per variant. Both samples restart the chain from the same seed while reusing the initialized dataset/VM. The profile.stderr phase counters aggregate both timed samples. Eight generated programs execute per hash.

Phase times include timestamp overhead and any scheduling interruption within a phase. Time outside the five phases includes scratchpad initialization, final hashing, wrapper work, and profiling bookkeeping. This is wall-time instrumentation, not hardware-PMU attribution or a measurement of individual instruction latency. Secure-JIT transition time on macOS includes its existing instruction-cache flush, so it is not directly equivalent to Linux mprotect time alone.

Reproduction

Extract the source archive into a fresh directory, place bench.cpp, build.py and final.py beside the source directory, then run python3 build.py and python3 final.py m4 (or hetzner / graviton). Use a fresh destination to preserve previous results; scripts deliberately fail if result directories already exist.

Graviton compilation uses the existing attested-relay-arm-toolchain:20260909 container, network disabled, with a bind-mounted experiment directory. Binaries then execute natively on the host. Named build containers remain stopped. No files or directories were deleted.

Native targeting affects compiler-generated library code. The RandomX VM's hand-written instruction generator still emits its existing instruction sequences; ARCH=native does not automatically redesign those sequences for Neoverse V3. Any measured improvement is specific to these builds and conditions, not proof that all JIT optimization opportunities have been exhausted.