x86-64, ARM and RISC-V: Three Families, Three Histories
Three instruction sets dominate current computing, and their differences are largely differences of origin and constraint rather than of achievable performance. Knowing what each optimised for explains their shape better than any claim about which is better.
Software view, hardware view
The gap between what you wrote and what the machine does is where this whole domain lives.
Three families, and what each was designed under
x86-64 descends from a design lineage stretching back decades, extended repeatedly while preserving backward compatibility throughout. Its encoding is variable-length, its architectural register count is modest by modern standards, and it carries genuine historical accretion — all of which are consequences of never having broken compatibility.
AArch64 is ARM's 64-bit architecture, and it had the advantage of being designed comparatively recently with the benefit of hindsight. Fixed-length instructions, more architectural registers, and a memory model that permits more reordering — a choice that trades some ease of concurrent programming for implementation freedom, and one that reflects ARM's origins in power-constrained designs.
RISC-V is an open standard rather than a proprietary architecture, structured as a small mandatory base plus optional extensions. Its distinguishing feature is not technical elegance so much as licensing: anyone may implement it without a licence fee, which is why it appears in research, in education and increasingly in embedded and specialised silicon.
| x86-64 | AArch64 | RISC-V | |
|---|---|---|---|
| Instruction encoding | Variable length | Fixed length | Fixed length base, optional compressed extension |
| Architectural GPRs | Fewer | More | More |
| Memory ordering | Relatively strong | Weaker — more reordering permitted | Weaker; explicit ordering instructions |
| Design origin | Decades of backward-compatible extension | Clean-sheet 64-bit design | Open standard, modular by construction |
| Licensing | Proprietary | Proprietary, licensed | Open — no licence fee to implement |
| Where you meet it | Desktops, most servers | Mobile, laptops, a growing server share | Embedded, research, specialised accelerators |
Variable versus fixed length, concretely
The encoding difference is the one most often discussed and the one with the clearest engineering consequence. On a variable-length ISA, an instruction may occupy anywhere from one byte to many, so a decoder cannot know where the next instruction begins until it has partially decoded the current one — which makes decoding several instructions per cycle genuinely hard, and is why high-performance implementations invest heavily in caching already-decoded operations.
On a fixed-length ISA, instruction boundaries are known in advance. Decoding several at once is straightforward, since their positions are arithmetic. The cost is code density: some operations that a variable-length encoding expresses compactly need more bytes, which is why several fixed-length ISAs add optional compressed encodings to claw density back.
The honest summary is that this is a real trade with real consequences for decoder area and power, and that it is not the dominant term in delivered performance on high-end implementations — where the front end is heavily engineered specifically to make it stop being one. It matters most where there is no budget for that engineering.
Variable-length (x86-64 style)
offset 0x00: 48 89 e5 ; 3 bytes
offset 0x03: 48 83 ec 20 ; 4 bytes
offset 0x07: 8b 45 fc ; 3 bytes
offset 0x0a: ...
-> the start of instruction N+1 is not known until N is decoded,
so parallel decode requires speculation or pre-decode metadata.
Fixed-length (AArch64 style)
offset 0x00: aa0003e1 ; 4 bytes
offset 0x04: 91008021 ; 4 bytes
offset 0x08: b9400fe0 ; 4 bytes
-> every boundary is offset + 4k, so N instructions can be
fetched and decoded in parallel without ambiguity.
(Byte values are illustrative of shape, not disassembly of real code.)The differences that actually reach your code
Two differences matter to a working programmer, and neither is encoding. The first is memory ordering. x86-64 provides comparatively strong ordering guarantees; AArch64 permits more reordering. Concurrent code with a missing synchronisation annotation can work by accident on the stronger model and fail on the weaker one — which is exactly the shape of bug that surfaces when a service is ported to a different architecture after years of running fine (Hardware Memory Models Are Not Language Memory Models, Why Your Loads and Stores Happen Out of Order).
The second is extension baselines. All three families are really a base plus a large set of optional extensions. Building with extensions your deployment hardware lacks produces a binary that fails at startup or, worse, when it first reaches the offending code path. This is a routine cross-architecture deployment problem and it is entirely avoidable by specifying the baseline explicitly at build time.
Everything else — register count, encoding, philosophical lineage — reaches you only through the compiler, which handles it. These two reach you directly, and both are worth checking before a port rather than after.
- Memory ordering differs, and code that relies on the stronger model can fail on the weaker one.
- Extension baselines differ; build for the baseline you deploy to, not the one you compile on.
- Register count changes generated code quality but is handled by the compiler.
- Encoding affects decoder design far more than it affects your program.
- Before porting: audit synchronisation annotations and pin the ISA baseline. Those two are the ones that bite.
| Difference | Reaches your code how? | What to do before porting |
|---|---|---|
| Memory ordering | Concurrency bugs that never appeared on the stronger model | Audit synchronisation annotations; test on the weaker model |
| Extension baseline | Illegal instruction at startup or on a rare path | Pin the baseline explicitly at build time |
| Register count | Different spilling behaviour in generated code | Nothing — the compiler handles it |
| Instruction encoding | Nothing directly | Nothing |
| Instruction timing | Different hot spots | Re-profile on the target; do not extrapolate |
Key points
- The three families differ mainly in encoding, register count, memory ordering strength and licensing — not in achievable performance.
- Variable-length encoding makes parallel decode hard; fixed-length makes it easy at some cost in code density.
- x86-64 orders memory more strongly than AArch64, which is where cross-architecture concurrency bugs come from.
- All three are a base plus optional extensions; the baseline you build for is your minimum deployable hardware.
- Everything except ordering and extensions reaches you only through the compiler.
Follow the mechanism
The path through the machine, hop by hop — and the conclusions it invites that are wrong.
- 1Source → compiler: a target ISA and extension baseline are selected, explicitly or by defaulting to the build machine.
- 2Compiler → encoding: instructions are emitted in the target's format, variable or fixed length.
- 3Binary → decoder: fixed-length boundaries are computed; variable-length boundaries must be discovered by decoding.
- 4Concurrent code → memory model: the architecture's ordering guarantees determine whether the synchronisation is sufficient.
- 5Runtime → extension check: an instruction outside the hardware baseline faults rather than executing.
- • "It compiles for both architectures, so it works on both." Compilation checks the instruction set, not the memory model your code assumes.
- • "RISC-V is faster because it is a cleaner design." Openness and modularity are its distinguishing properties; performance belongs to implementations.
- • "Fixed-length encoding means faster." It makes parallel decode easier and costs code density. Neither dominates delivered performance on high-end designs.
Consequences, controls and cost
- • Concurrent code correct on x86-64 can exhibit real ordering bugs when ported to AArch64.
- • A binary built with newer extensions fails on older hardware, sometimes only on a rarely-taken path.
- • Generated code for the same source differs in register spilling behaviour between ISAs with different register counts.
- • Audit synchronisation annotations before porting to a weaker memory model — do not rely on testing to find the gaps.
- • Pin the ISA baseline explicitly in build configuration rather than inheriting the build machine's capabilities.
- • Test on the target architecture in CI; cross-architecture behaviour differences do not show up on the developer machine.
- • Use runtime feature detection with fallbacks where extension-dependent code paths are worth the complexity.
- • Run the concurrency test suite on the weaker memory model specifically; passing on x86-64 tells you very little about AArch64.
- • Inspect built binaries for the extensions they actually require before deploying to a different hardware baseline.
- • Benchmark on the target architecture rather than extrapolating from a different one.
- • Supporting multiple architectures multiplies build, test and debugging surface for real portability.
- • A conservative extension baseline maximises compatibility and forgoes performance available on newer hardware.
Scope
§224 — what these claims are specific to.
- ISA-SPECIFICEvery claim here is about a specific instruction set family; encoding, register count and ordering guarantees differ between all three.
- SIMPLIFIEDEach family spans many profiles and extension sets. "AArch64" and "RISC-V" in particular cover configurations with materially different capabilities.