A CPU runs a program by fetching instructions from memory, decoding them into internal operations, and executing them as fast as their data allows. Modern cores overlap dozens of instructions in a pipeline, guess branch outcomes before they are known, run independent work out of order, hide memory delay with caches, and apply one instruction to many values at once with SIMD. Results still appear in program order.
This post walks through each of those mechanisms at the level a performance engineer would use day to day, using the vendors' own optimization manuals for every microarchitecture figure. It belongs to our hardware series alongside how AI chips are designed, what an AI accelerator is and how a GPU is made, from wafer to HBM package. Where a vendor has not published a figure in a source we could read, we say so instead of filling the gap.
Key takeaways
- ISA and microarchitecture are different things. x86-64, Arm A64 and RISC-V define what instructions mean. Golden Cove, Zen 4 and Neoverse N2 are specific designs that implement an ISA, and they differ in width, buffer sizes and cache layout.
- A modern core is in order at both ends and out of order in the middle. Fetch, decode and rename happen in program order; execution happens whenever operands are ready; retirement puts results back in program order.
- The front end is a prediction machine. Intel's Golden Cove decodes up to six instructions per cycle and its micro-op cache delivers up to eight micro-ops per cycle, according to Intel's optimization manual. None of that helps if the branch predictor sends fetch down the wrong path.
- Branch prediction accuracy matters more than it looks. In a worked example with stated assumptions, going from 95% to 99% accuracy moves a 4-wide core from 2.5 to about 3.6 instructions per cycle.
- Caches and TLBs decide most real performance. Intel lists a 5-cycle L1 data cache and a 13-cycle L2 for Ice Lake client cores. A miss to DRAM costs hundreds of cycles, which no reorder buffer can fully hide.
- SIMD multiplies throughput, not latency. A 128-bit vector holds 4 single-precision floats, 256 bits holds 8, 512 bits holds 16. Arm SVE leaves the length to the implementation, from 128 to 2048 bits.
- Speculation leaks. Spectre and Meltdown, disclosed in January 2018, showed that work the CPU throws away can still change cache state an attacker can measure. Fixes span microcode, kernels, compilers and browsers.
- CPU microcode and firmware are software you have to track. Intel publishes Linux microcode releases on GitHub (the newest at the time of writing is tagged microcode-20260925), and Linux loads them early from the initrd.
How to read the numbers in this post
Three kinds of numbers appear below, and we label each one.
- Vendor figures. Decode width, cache sizes, latencies and port counts come from Intel's Optimization Reference Manual (documentation changes edition 355308-002, August 2023, which carries the full revised text of manual 248966-048), Arm's Neoverse N2 Software Optimization Guide (Issue 5.0, 31 October 2022) and AMD's Ryzen Processor Software Optimization presentation from GDC 2024 (March 2024). We cite each one where it appears.
- Worked examples. Arithmetic we did ourselves from vendor figures or from clearly stated assumptions. Every computed number was checked with Python before it went into the text.
- Assumptions. Where a vendor does not publish a figure we need (for example a branch misprediction penalty or a DRAM latency), we pick a round number, say that it is an assumption, and never present it as a measurement.
We report no benchmark results. The C code in this post was compiled and checked for correct output on an arm64 Mac; we did not time it. The AVX2 example cannot run on that machine and was not executed; it follows Intel's intrinsics naming but is untested.
A short timeline of the ideas in this post
| Date | Event | Source |
|---|---|---|
| 2001 | Jiménez and Lin publish "Dynamic Branch Prediction with Perceptrons" at HPCA, using a simple neural model for branch prediction | Jiménez and Lin, HPCA 2001 |
| February 2006 | Seznec and Michaud publish "A case for (partially) TAgged GEometric history length branch prediction", the TAGE paper | Journal of Instruction-Level Parallelism, Vol. 8 |
| 1 June 2017 | Google Project Zero reports speculative execution issues to Intel, AMD and Arm | Project Zero, January 2018 |
| January 2018 | Spectre (CVE-2017-5753, CVE-2017-5715) and Meltdown (CVE-2017-5754) become public | Spectre paper, Meltdown paper, Linux kernel docs |
| March 2018 | Arm's SVE paper is posted to arXiv, describing vector-length agnostic SIMD | Stephens et al., arXiv:1803.06185 |
| 31 October 2022 | Arm releases Issue 5.0 of the Neoverse N2 Software Optimization Guide | Arm N2 SWOG |
| August 2023 | Intel publishes revised optimization manual text covering Golden Cove and Sapphire Rapids | Intel changes document 355308-002 |
| March 2024 | AMD presents Zen 4 core details and cache hierarchy at GDC 2024 | AMD GDC 2024 deck |
| 25 September 2026 | Intel tags microcode-20260925 in its Linux microcode repository | Intel microcode repository |
From transistors to instructions: what is the difference between an ISA and a microarchitecture?
At the bottom of a CPU are transistors wired into logic gates, gates wired into adders, multiplexers and flip-flops, and those wired into registers and functional units. A clock signal tells every flip-flop when to capture its input. Everything a processor does reduces to moving bits between registers through combinational logic once per clock edge.
Programmers never see that layer directly. They see an instruction set architecture (ISA): the contract that says which registers exist, which instructions exist, what each one does to registers and memory, how exceptions behave, and how memory ordering works between cores. The ISA is a specification. Hennessy and Patterson's Computer Architecture: A Quantitative Approach draws this line carefully, and it is worth keeping in mind for everything that follows.
A microarchitecture is one particular implementation of an ISA. Intel's Golden Cove and AMD's Zen 4 both implement x86-64; they run the same binaries but have different decoders, buffers, execution ports and caches. Arm licenses the A64 ISA and also designs cores such as Neoverse N2; other companies design their own A64 cores. RISC-V publishes the ISA as an open specification through RISC-V International, and many independent designs implement it.
| Aspect | x86-64 | Arm A64 (AArch64) | RISC-V (RV64) |
|---|---|---|---|
| Instruction length | Variable length | Fixed 32-bit | 32-bit base, optional 16-bit compressed (C extension) |
| General purpose registers | 16 | 31 plus a zero/stack register | 31 plus hard-wired zero (x0) |
| Memory operands in arithmetic | Yes (load-op forms) | No, load/store architecture | No, load/store architecture |
| SIMD extensions | SSE, AVX, AVX2, AVX-512 | NEON (Advanced SIMD), SVE, SVE2 | V extension (vector) |
| Specification owner | Intel and AMD manuals (for example the AMD64 Architecture Programmer's Manual) | Arm Architecture Reference Manual | RISC-V International ratified specifications |
Variable-length x86 instructions are the main reason x86 front ends are harder to build. A decoder cannot know where instruction three starts until it has worked out how long instructions one and two are. Fixed-length A64 and RISC-V instructions make finding boundaries trivial. As we will see, x86 designs answer this partly with a micro-op cache that stores already-decoded work.
One more term: microcode. Some complex instructions are not implemented directly in hardware. The decoder hands them to a microcode sequencer (Intel's diagrams label it MSROM), which emits a series of simpler internal operations stored in on-chip memory. Vendors can patch that behaviour at boot by a microcode update, which is how vendors fix certain errata and add mitigations after a chip ships. That makes microcode a software supply chain component, and we return to it near the end.
What is the fetch, decode, execute cycle?
The textbook model has a CPU repeat four steps: fetch the instruction at the program counter, decode it, execute it, and write the result back. A simple design does each step for one instruction before starting the next.
Unpipelined: one instruction at a time
cycle: 1 2 3 4 5 6 7 8 9 10
instr A: [F] [D] [E] [W]
instr B: [F] [D] [E] [W]
instr C: [F] [D] ...
That wastes almost all the hardware almost all the time. The fetch unit sits idle while the execute unit works. Pipelining fixes this by overlapping instructions, like an assembly line.
Classic 5-stage pipeline (IF, ID, EX, MEM, WB)
cycle: 1 2 3 4 5 6 7 8 9
instr A: IF ID EX MEM WB
instr B: IF ID EX MEM WB
instr C: IF ID EX MEM WB
instr D: IF ID EX MEM WB
instr E: IF ID EX MEM WB
Once the pipeline is full, one instruction completes every cycle even though each one takes five cycles end to end. Throughput improves by up to the number of stages; latency for any single instruction does not improve at all.
Three kinds of hazards break the ideal:
- Data hazards. Instruction B needs a value A has not produced yet. Forwarding (bypass) paths send a result straight from the execute stage back to the next instruction's input, but a load followed immediately by a use still has to wait.
- Control hazards. After a branch, the pipeline does not yet know which instruction comes next. Waiting costs as many cycles as the stages before the branch resolves. Guessing (branch prediction) avoids the wait when the guess is right.
- Structural hazards. Two instructions need the same hardware in the same cycle, such as a single memory port.
Every technique in the rest of this post is a response to one of those three hazards, scaled up to a machine that handles several instructions per cycle.
How does a modern out-of-order core pipeline work?
A current high-performance core is superscalar (it handles several instructions per cycle) and out of order (it executes instructions when their inputs are ready, not strictly in program order). Here is the overall shape, using labels that match the vendor manuals.
IN ORDER (front end) OUT OF ORDER (back end) IN ORDER
+---------------------------------------------+ +-------------------------------------+ +-----------+
| Branch -> Fetch -> Pre- -> Decode -----+--> Rename/ -> Schedulers -> Exec | | Retire |
| predictor (I-$, decode (x86: 4-6 | | allocate (reservation ports | | (commit |
| (BTB, ITLB) (x86 wide) | | (RAT, stations) ALU, |->| in prog- |
| TAGE-like, length) | | free list, FP, | | ram |
| RAS) \ | | ROB entry) LD/ST | | order) |
| +-> Micro-op cache -----+ | | | |
| (decoded ops) | | Load/store queues, store buffer | | |
+---------------------------------------------+ +-------------------------------------+ +-----------+
^ results forwarded on bypass network
The front end's job is to deliver a steady stream of decoded operations. The back end's job is to execute as many of them per cycle as dependencies allow. The retirement stage makes the result look exactly as if instructions had run one at a time in order, which is what the ISA promises.
What do Golden Cove, Zen 4 and Neoverse N2 look like on paper?
The table below collects figures each vendor published in the source we read. Blank cells mean "not stated in that source", not "zero".
| Feature | Intel Golden Cove (P-core) | AMD Zen 4 | Arm Neoverse N2 |
|---|---|---|---|
| ISA | x86-64 | x86-64 | Armv9-A (A64) |
| Decode | 6 instructions per cycle (up from 4) | 4 instructions per cycle | Not stated as a single figure; dispatch handles up to 5 macro-ops per cycle |
| Legacy fetch bandwidth | 32 bytes per cycle (up from 16) | Not stated in the deck | Not stated |
| Decoded-op cache | Micro-op cache, delivers up to 8 micro-ops per cycle; size increased, entry count not given in the text | Op cache of 6.75K ops (up from 4K), delivers 9 macro-ops per cycle | Not stated in the guide |
| Allocation or dispatch | 6 micro-ops per cycle | 6 macro-ops per cycle dispatched | Up to 5 macro-ops and up to 10 micro-ops per cycle |
| Execution ports or issue pipelines | 12 ports (up from 10) | 4 ALU, 3 AGU and FP pipes shown in the block diagram | 13 issue pipelines, each accepting 1 micro-op per cycle |
| Retirement | 8 micro-ops per cycle (up from 4) | Not stated in the deck | Not stated in the guide |
| Loads per cycle | 3 (up from 2) | 3 loads and 2 stores per cycle | Two load/store pipes plus one load pipe |
| L1 instruction cache | 32 KB | 32 KB, 8-way | Separate L1 I and D caches; sizes not stated in the guide |
| L1 data cache | 48 KB | 32 KB, 8-way | Not stated in the guide |
| L2 | 1.25 MB client, 2 MB server | 1 MB per core, 8-way | Private, unified per core; size not stated in the guide |
| L3 | Shared, size depends on product | 32 MB per core complex die (CCD) | System level, depends on the SoC |
| SIMD | SSE, AVX2; AVX-512 and AMX on server parts | AVX-512 support added in Zen 4 | NEON plus SVE and SVE2 at 128-bit vector length |
| Reorder buffer entries | Not stated in the text we read (the manual says it was significantly enlarged) | Not stated in the deck (AMD's guide 57647 covers the retire queue; we could not retrieve it) | Not stated in the guide |
Sources: Intel optimization manual section 2.3 (Golden Cove); AMD GDC 2024 deck slides 14, 18 and 26; Arm N2 guide sections 1, 2.1 and 4.1. The Intel figures appear in the manual as change-tracked text, for example "4 6 decoders", which we read as four becoming six.
You will see larger reorder buffer numbers quoted for these cores in reviews and conference talks. We left them out because the vendor documents we could retrieve do not state them, and this post only uses figures from vendor sources.
Fetch: where does the next instruction come from?
Fetch reads a block of bytes from the L1 instruction cache at the address the branch predictor chose. On Golden Cove the legacy decode path fetches up to 32 bytes per cycle, double the previous 16, according to Intel's manual. An instruction TLB translates the virtual fetch address to a physical one; Intel doubled Golden Cove's ITLB to 256 entries for 4 KB pages and 32 entries for 2 MB or 4 MB pages.
A useful worked number: 256 ITLB entries times 4 KB pages covers 1 MB of code. A server binary whose hot code is spread across much more than that will miss in the ITLB regularly unless the code is mapped with large pages, which is one reason large code footprints get their own bullet in Intel's Golden Cove list ("improvements for large code footprint workloads").
The fetch unit does not wait for the previous block to decode. It runs ahead, steered by the branch predictor, and fills a queue. Intel calls the queue between the front end and the out-of-order engine the IDQ; on Golden Cove it holds 144 micro-ops per logical processor in single-thread mode, or 72 per thread with SMT active.
Decode: macro-ops, micro-ops and the micro-op cache
Decoding turns architectural instructions into the internal operations the back end actually executes. Vendors use slightly different words:
- Intel calls them micro-ops (uops). A simple instruction becomes one; a load-and-add becomes a fused pair that is tracked as one in the front end and split later ("micro-fusion"). A compare followed by a conditional jump can merge into one ("macro-fusion").
- AMD calls the front-end unit a macro-op, which can carry an ALU operation and a memory operation together.
- Arm's N2 guide says instructions decode into macro-operations (MOPs), and "a MOP can be split into two micro-operations" after decode.
x86 decode is expensive because of variable lengths. Both Intel and AMD therefore keep a decoded-op cache that stores the result of earlier decodes, indexed by instruction address. When the hot loop fits in it, the legacy decoders can sit idle (saving power), and the op cache delivers more operations per cycle than the decoders could. Intel's manual summarizes the two advantages of its decoded ICache plainly: the front end does not need to decode code already in it, which saves power, and switching between the decoded ICache and legacy decode carries a penalty. Golden Cove's micro-op cache delivers up to 8 micro-ops per cycle; AMD's Zen 4 op cache holds 6.75K ops and delivers 9 macro-ops per cycle, versus 4 instructions per cycle from its decoders.
A practical consequence: two builds of the same program can differ in speed just because a hot loop's code layout straddles op cache lines differently. Compilers pad and align loop heads partly for this reason.
Rename: removing false dependencies
The ISA gives a program only a few architectural registers (16 general purpose on classic x86-64, 31 on A64). Compilers reuse them constantly, which creates dependencies that are about names, not data.
1: r1 = load [a]
2: r2 = r1 + 4
3: r1 = load [b] # writes r1 again (WAW with 1, WAR with 2)
4: r3 = r1 * 2
Instruction 3 does not need anything from 1 or 2. It only reuses the name r1. Without renaming, 3 would have to wait until 2 has read r1. With renaming, the core keeps a much larger physical register file and a register alias table (RAT) that maps each architectural register to its current physical register.
Before rename RAT update After rename
1: r1 = load [a] r1 -> p17 p17 = load [a]
2: r2 = r1 + 4 r2 -> p18 p18 = p17 + 4
3: r1 = load [b] r1 -> p19 p19 = load [b]
4: r3 = r1 * 2 r3 -> p20 p20 = p19 * 2
Now chains 1 to 2 and 3 to 4 are independent and can run in parallel. Only true data dependencies (read after write) remain. Rename also handles two cheap tricks that Intel's Golden Cove diagram lists by name at the allocate stage: move elimination (a register-to-register copy just updates the RAT and never executes) and zero idioms (instructions like xor eax, eax are recognised as "set to zero" with no dependency on the old value).
Rename is also where each operation gets an entry in the reorder buffer (ROB) and, if it is a load or store, in the load or store queue. When any of those structures is full, the front end stalls. Arm's N2 guide spells out the dispatch limits per cycle: up to 5 macro-ops and 10 micro-ops, with no more than 4 using the single-cycle integer or branch pipes, 4 using the multi-cycle integer pipes, and 2 each on the V0 and V1 vector pipes.
The reorder buffer and retirement: keeping order while executing out of order
The ROB is a circular queue in program order. Every in-flight operation has an entry. Operations can finish in any order, but they retire (commit) only from the head of the ROB, oldest first, and only when they have finished without a fault.
ROB (oldest at head)
head -> [ld p17 done ] [add p18 done ] [ld p19 waiting on cache miss] [mul p20 waiting on p19]
[add p21 done ] [st ... done ] [br ... done, predicted correctly ] ... <- tail (newest)
Retire this cycle: ld p17, add p18 (stop at the first unfinished entry: ld p19)
Retirement in order gives the CPU precise exceptions: if instruction 3 faults, everything older has committed and nothing younger has, so the operating system sees a clean state. It also gives a clean way to undo speculation. If a branch turns out to be mispredicted, every ROB entry younger than the branch is discarded and the RAT is restored to the branch's checkpoint.
How big does the ROB need to be? Little's law gives the intuition. To keep a core busy at 4 instructions per cycle while a single load waits 300 cycles for DRAM (an assumed round number), you would need 300 times 4, or 1,200 instructions in flight. No shipping ROB is that large, which is why a cache miss to DRAM usually stalls retirement eventually, and why prefetching and memory-level parallelism (several misses outstanding at once) matter so much. Intel's Golden Cove list mentions exactly that: more outstanding misses and deeper miss queues.
Schedulers and execution ports
After rename, operations wait in schedulers (Intel also uses the older term reservation station). Each cycle, a scheduler picks ready operations, usually oldest first, and issues them to an execution port. A port is a doorway to one or more functional units.
Intel's Table 2-1 for Golden Cove lists the ports. Its pipeline figure shows integer ALUs on ports 0, 1, 5, 6 and 10 (the manual describes the fifth integer ALU port as having "expanded capability"); ports 2, 3 and 11 handle loads; ports 7 and 8 compute store addresses; ports 4 and 9 carry store data. The vector stack hangs off ports 0, 1 and 5, with FMA units and a new fast floating-point adder. Arm's N2 lists 13 issue pipelines: two branch, two single-cycle integer, two single/multi-cycle integer, two FP/ASIMD, two load/store, one load-only and two store-data.
Neoverse N2 back end (from Arm's Figure 2-1)
Dispatch (in order) ---> Issue (out of order)
+--> Branch 0, Branch 1
+--> Integer single-cycle 0, 1 (S pipes)
+--> Integer single/multi-cycle 0, 1 (M pipes: mul, div, CRC)
+--> FP/ASIMD 0, FP/ASIMD 1 (V0, V1: FP add, FP mul, FMA, crypto)
+--> Load/Store 0, Load/Store 1, Load 2
+--> Store data 0, Store data 1
Two numbers describe each operation on a port: latency (cycles until a dependent operation can use the result) and throughput (how many can start per cycle across all eligible pipes). Arm's N2 guide lists scalar floating-point FADD with latency 2 and throughput 2 on the V pipes. Both numbers matter, and the worked loop example later shows how.
Loads, stores and memory disambiguation
Loads are the most performance-critical operations because so much depends on them. Three structures handle memory:
- The load queue tracks every in-flight load in program order.
- The store queue (and store buffer) holds stores until they retire and can be written to the L1 cache. Stores never write to cache speculatively.
- Store-to-load forwarding lets a load read data straight from an older, not yet committed store to the same address.
A harder problem is that a load may want to run before an older store whose address is not yet known. If the core waits, it loses parallelism; if it runs the load early and the store turns out to write the same address, the load got stale data. Modern cores predict which loads are safe to hoist (memory dependence prediction), run them early, and check when the store address resolves. If they guessed wrong, they flush and replay from the load. That check is another form of speculation, and like branch prediction it later turned out to have security consequences.
How does branch prediction work?
Branches are everywhere. Loops end with one, if statements compile to one, function calls and returns are branches, and virtual calls are indirect branches. A wide core fetches far ahead, so it must decide where every branch goes long before the branch executes.
What does a misprediction cost?
Here is a worked example with assumptions stated up front.
- One instruction in five is a branch (assumption).
- A misprediction costs 15 cycles of lost work (assumption; vendors describe the penalty in their manuals for specific cases but we use a round number).
- The core could otherwise sustain 4 instructions per cycle, a base CPI of 0.25.
| Prediction accuracy | Added cycles per instruction | Total CPI | Instructions per cycle |
|---|---|---|---|
| 95% | 0.2 × 0.05 × 15 = 0.15 | 0.40 | 2.5 |
| 99% | 0.2 × 0.01 × 15 = 0.03 | 0.28 | about 3.6 |
| 99.9% | 0.2 × 0.001 × 15 = 0.003 | 0.253 | about 3.95 |
Going from 95% to 99% raises throughput by about 43% in this model. That is why vendors keep listing "improved branch prediction" in every generation; Intel and AMD both do so for Golden Cove and Zen 4.
The pieces of a branch prediction unit
A branch prediction unit (BPU) answers three separate questions, each with its own structure.
- Is there a branch here, and where does it go? The branch target buffer (BTB) is a cache keyed by fetch address that remembers where taken branches went. It lets fetch redirect before the instruction is even decoded.
- Is this conditional branch taken? The direction predictor, which is where TAGE-style designs live.
- Where does this return go? A return address stack (RAS) pushes on calls and pops on returns. Indirect branches with several targets get their own predictor, also history-based.
From two-bit counters to global history
The simplest useful direction predictor is a table of 2-bit saturating counters indexed by branch address. States 0 and 1 predict not taken; 2 and 3 predict taken. One surprise outcome does not flip a strongly biased branch, so a loop branch that is taken 99 times and falls through once mispredicts only once per loop.
2-bit saturating counter
taken taken taken
[0 strong NT] --> [1 weak NT] --> [2 weak T] --> [3 strong T]
<-- <-- <--
not taken not taken not taken
Counters indexed only by address cannot learn correlations between branches, such as "if the previous branch was taken, this one is not". Global history predictors keep a shift register of recent outcomes and hash it with the branch address (the gshare scheme XORs them) to pick a counter. Now the same branch can have different predictions in different contexts.
The catch is history length. Short histories learn fast and need little storage; long histories capture correlations far apart but split the training data across many more table entries. No single length is right for every branch.
Perceptrons and TAGE
Two research lines took different answers to the history length problem.
Jiménez and Lin's 2001 HPCA paper used a perceptron per branch: a vector of small signed weights, one per history bit. The prediction is the sign of the dot product of weights and history. Its key property, quoted from their abstract, is that "the hardware resources for our method scale linearly with the history length", where table-based schemes grow exponentially.
Seznec and Michaud's 2006 paper introduced TAGE (TAgged GEometric history length). TAGE keeps a simple base predictor plus several tagged tables, each indexed by a hash of the branch address with a different history length. The lengths form a geometric series, so a few tables span from very short to very long histories.
TAGE structure (conceptual)
PC ----------------------------------+--> T0: base predictor (bimodal counters, untagged)
PC xor hash(history[0:L1]) ---------+--> T1: tagged, short history
PC xor hash(history[0:L2]) ---------+--> T2: tagged
PC xor hash(history[0:L3]) ---------+--> T3: tagged
... ...
PC xor hash(history[0:Ln]) ---------+--> Tn: tagged, longest history
Each tagged entry: [ tag | 3-bit prediction counter | useful bits ]
Prediction = entry from the longest-history table whose tag matches (the "provider");
the next-longest match is the "alternate prediction".
To see why geometric lengths are efficient, here is an illustrative series of our own (not the paper's configuration) with 12 tables from a 4-branch history to a 640-branch history. The ratio between neighbours is about 1.59.
| Table | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| History length (branches) | 4 | 6 | 10 | 16 | 25 | 40 | 64 | 101 | 160 | 254 | 403 | 640 |
Twelve tables cover two orders of magnitude of history. Tags mean an entry only answers for the context that allocated it, which cuts destructive aliasing. On a misprediction TAGE allocates a new entry in a longer-history table, so each branch climbs to the shortest history that predicts it well. Useful bits let entries that keep helping survive replacement.
Vendors describe their predictors only loosely in public manuals ("bigger branch prediction structures" for Golden Cove, "improved branch prediction" for Zen 4), so we cannot tell you which exact design a given core uses. TAGE and its descendants are the usual reference point in published branch prediction research, so it is the right design to learn first.
Writing branch-friendly code
You rarely need to think about prediction, but three habits help in hot loops:
// 1. A data-dependent branch on random data mispredicts often.
for (size_t i = 0; i < n; i++)
if (a[i] >= 128) sum += a[i];
// 2. The same logic without a branch: compilers usually emit a
// conditional move or a vector compare-and-mask here.
for (size_t i = 0; i < n; i++)
sum += (a[i] >= 128) ? a[i] : 0;
// 3. Or sort/partition the data first so the branch becomes predictable.
Profile before rewriting. A branch that is 99% predictable is nearly free, and a conditional move adds a data dependency that can be slower than a well-predicted branch.
How do CPU caches work?
DRAM is far slower than a core. Intel's manual lists the Ice Lake client L1 data cache at 5 cycles and the L2 at 13 cycles of load latency, with the L3 latency marked as dependent on core count. A trip to DRAM is commonly hundreds of cycles; we use an assumed 300 below. Caches exist because programs reuse data (temporal locality) and touch nearby data (spatial locality).
The hierarchy
Core
+-------------+
| registers | ~ hundreds of bytes, 0 cycles extra
| L1 I | L1 D | 32-48 KB each, ~5 cycles (Intel Ice Lake L1D: 5)
+------+------+
| L2 | 0.5-2 MB, ~13 cycles (Intel Ice Lake L2: 13)
+-------------+
|
+---------------------------+
| L3 / last level cache | shared, tens of MB (AMD Zen 4: 32 MB per CCD)
+---------------------------+
|
+---------------------------+
| DRAM (DDR5, LPDDR5) | GBs, hundreds of cycles (assumed 300 below)
+---------------------------+
Intel's Ice Lake client table also lists peak L1 bandwidth as two 64-byte loads plus one 64-byte store per cycle, L2 peak of 64 bytes per cycle with 48 sustained, and L3 peak of 32 bytes per cycle with 21 sustained. Those peak versus sustained columns are a good reminder that "peak" is a ceiling, not a typical result.
AMD's GDC deck gives the Zen generations side by side: Zen 4 has a 6.75K op cache, 32 KB L1I, 32 KB L1D, 1024 KB L2 per core and 32 MB of L3 per core complex. Zen 3 had a 4K op cache and 512 KB of L2.
Lines, sets and ways: decoding an address
Caches move data in lines, 64 bytes on all three vendors' designs discussed here. A set-associative cache splits into sets; each address maps to one set and can live in any of that set's ways.
Worked example for a 32 KB, 8-way cache with 64-byte lines, the Zen 4 L1D geometry from AMD's slide:
- Sets = 32,768 ÷ (64 × 8) = 64.
- Offset bits = log2(64) = 6 (which byte in the line).
- Index bits = log2(64 sets) = 6 (which set).
- Each way is 4,096 bytes, the same as a 4 KB page.
virtual/physical address
+--------------------------------+-------------+-----------+
| tag | index (6 b) | offset 6b |
+--------------------------------+-------------+-----------+
bits 11..6 bits 5..0
That last bullet is not a coincidence. Index plus offset is 12 bits, exactly the 4 KB page offset, which does not change in address translation. So the L1 can start indexing with the virtual address while the TLB translates the upper bits in parallel, then compare physical tags. This is called virtually indexed, physically tagged (VIPT). With 4 KB pages it pushes designers to grow an L1 by adding ways rather than sets, because each way can be at most one page in size if the index is to stay inside the page offset.
Average memory access time, worked
Average memory access time (AMAT) chains hit times and miss rates. Using Intel's Ice Lake client L1 and L2 latencies plus two assumptions (a 50-cycle L3 and a 300-cycle DRAM), and assumed hit rates of 95% in L1, 80% of L1 misses hitting in L2, and 70% of L2 misses hitting in L3:
L3 term = 50 + (1 - 0.70) × 300 = 140 cycles
L2 term = 13 + (1 - 0.80) × 140 = 41 cycles
AMAT = 5 + (1 - 0.95) × 41 = 7.05 cycles
Drop the L1 hit rate to 90% and AMAT rises to 9.1 cycles. A five point drop in L1 hit rate costs about 29% more average latency in this model. Data layout, which decides hit rates, often matters more than instruction count.
Write policies, inclusion and prefetchers
Intel lists all three of its Ice Lake client cache levels as write-back: a store updates the line in cache and marks it dirty; memory is updated only when the line is evicted. Caches differ in inclusion: an inclusive L3 holds a copy of everything in the L2s above it, an exclusive or victim L3 holds only lines evicted from L2. AMD's deck notes that each core complex's L3 is shared by up to eight cores and keeps shadow tags for each L2 within its complex, which lets it decide whether a fast cache-to-cache transfer is possible without probing every core.
Hardware prefetchers watch access patterns and fetch lines before they are requested. AMD's GDC deck names its Zen 4 prefetchers: an L1 stream prefetcher (sequential lines), an L1 stride prefetcher (per-instruction strides), an L1 region prefetcher, and L2 stream and up/down prefetchers. It also warns that too many software prefetches can evict useful data.
Cache coherence: MESI and MOESI
With several cores each holding private caches, two copies of the same line can disagree. A coherence protocol keeps one rule true: at any moment, a line is either writable by exactly one cache or readable by any number of caches, never both. Hardware enforces it with states per line.
The classic MESI protocol, described by Papamarcos and Patel at ISCA in 1984 ("A Low-Overhead Coherence Solution for Multiprocessors with Private Cache Memories"), uses four states.
| State | Meaning | Can read locally | Can write locally | Memory up to date |
|---|---|---|---|---|
| M (Modified) | Only copy, dirty | Yes | Yes | No |
| E (Exclusive) | Only copy, clean | Yes | Yes, silently moves to M | Yes |
| S (Shared) | One of several clean copies | Yes | No, must invalidate others first | Yes |
| I (Invalid) | Not present | No | No | n/a |
MOESI, which AMD documents in its AMD64 Architecture Programmer's Manual, adds O (Owned): a dirty line that other caches may also hold in S. The owner supplies data to readers and writes it back later, so a modified line can be shared without first writing it to memory.
Here is a walk-through of two cores and one line:
step action core A core B notes
1 A reads X E I no one else has it
2 A writes X M I silent upgrade, no bus traffic
3 B reads X O S MOESI: A supplies data, stays owner
(S) (S) MESI: A writes back, both go to S
4 B writes X I M B must invalidate A's copy first
5 A reads X S/O... line moves back, more traffic
Step 4 is where performance goes to die if it happens constantly. False sharing is the common trap: two threads write different variables that happen to share a 64-byte line, and the line bounces between cores even though no data is actually shared.
// False sharing: both counters sit in one 64-byte line.
struct { long a; long b; } counters; // thread 1 writes a, thread 2 writes b
// Fix: give each its own line.
struct { _Alignas(64) long a; _Alignas(64) long b; } padded_counters;
TLBs and paging
Programs use virtual addresses. The memory management unit (MMU) translates them to physical addresses using page tables in memory, and caches recent translations in translation lookaside buffers (TLBs).
On x86-64 with 4-level paging, a 48-bit virtual address splits into four 9-bit table indexes plus a 12-bit page offset. 5-level paging adds a fifth level and 57-bit addresses. The Linux kernel's x86-64 memory map documentation shows what that means for user space: about 128 TB with 4-level page tables (2 to the power 47 bytes) and about 64 PB with 5-level tables (2 to the power 56).
48-bit virtual address, 4 KB pages, 4-level paging
47 39 38 30 29 21 20 12 11 0
+-----------+-----------+-----------+-----------+---------------+
| PML4 (9b) | PDPT (9b) | PD (9b) | PT (9b) | offset (12 b) |
+-----------+-----------+-----------+-----------+---------------+
| | | |
v v v v
table --> table --> table --> table --> physical page + offset
(a TLB miss costs up to four dependent memory reads; page walk caches cut this)
TLB reach is entries times page size. Intel's Ice Lake client table lists a 64-entry first-level data TLB for 4 KB pages and a 2,048-entry second-level TLB shared across page sizes, of which 2 MB pages can use 1,024 entries.
| TLB | Entries | Page size | Reach |
|---|---|---|---|
| Ice Lake L1 DTLB (loads) | 64 | 4 KB | 256 KB |
| Ice Lake STLB | 2,048 | 4 KB | 8 MB |
| Ice Lake STLB | 1,024 | 2 MB | 2 GB |
| Golden Cove ITLB | 256 | 4 KB | 1 MB |
A database with a 64 GB buffer pool walks page tables constantly with 4 KB pages and far less with 2 MB pages. That is why databases and JVMs offer huge page settings.
How does SIMD work?
SIMD (single instruction, multiple data) applies one operation to every lane of a wide register. It raises throughput per instruction; it does nothing for the latency of one operation.
| Extension | ISA | Register width | FP32 lanes | FP64 lanes | Notes |
|---|---|---|---|---|---|
| SSE to SSE4.2 | x86 | 128 bits (XMM) | 4 | 2 | Fixed width |
| AVX, AVX2 | x86 | 256 bits (YMM) | 8 | 4 | Fixed width |
| AVX-512 | x86 | 512 bits (ZMM), plus 8 mask registers | 16 | 8 | Golden Cove server parts; AMD added it in Zen 4; Alder Lake E-cores do not support it |
| NEON (Advanced SIMD) | Arm A64 | 128 bits | 4 | 2 | Fixed width |
| SVE, SVE2 | Arm A64 | 128 to 2048 bits, chosen by the implementation | 4 to 64 | 2 to 32 | Neoverse N2 implements 128 bits |
| V extension | RISC-V | Implementation-defined VLEN | VLEN ÷ 32 | VLEN ÷ 64 | Vector-length agnostic like SVE |
Sources: Intel optimization manual (AVX-512 and the note that Alder Lake E-cores lack it), AMD GDC 2024 deck (AVX-512 added in Zen 4), Arm N2 guide (SVE at 128-bit vector length), Stephens et al. on SVE.
Intel's manual is explicit that some Golden Cove vector features are "not available for 512-bit vectors in Client parts", and that on hybrid Alder Lake chips the E-cores do not support AVX-512. Software that dispatches on CPU features must check at runtime, not assume.
Fixed-width versus vector-length agnostic
SSE, AVX and NEON fix the width in the instruction set. Wider hardware needs new instructions and a recompile. Arm's SVE paper describes the alternative: code is written in terms of "however many lanes this machine has", using predicate registers to handle the tail of a loop. The same binary runs on a 128-bit and a 512-bit implementation. RISC-V's V extension takes a similar approach with a vsetvl instruction that tells the program how many elements the hardware will process this iteration.
SIMD in code
Here is the same reduction written three ways. The scalar and NEON versions compiled and produced matching results on an arm64 Mac; we did not time them. The AVX2 version cannot run on that machine and was not executed.
#include <stddef.h>
// Scalar: one add per element, one long dependency chain through s.
float sum_scalar(const float *a, size_t n) {
float s = 0.0f;
for (size_t i = 0; i < n; i++) s += a[i];
return s;
}
// Four independent accumulators: same instruction count, shorter chains.
float sum_4acc(const float *a, size_t n) {
float s0 = 0, s1 = 0, s2 = 0, s3 = 0;
size_t i = 0;
for (; i + 4 <= n; i += 4) {
s0 += a[i]; s1 += a[i + 1]; s2 += a[i + 2]; s3 += a[i + 3];
}
for (; i < n; i++) s0 += a[i];
return (s0 + s1) + (s2 + s3);
}
#include <arm_neon.h>
#include <stddef.h>
// NEON: 4 floats per vector, 4 vector accumulators = 16 sums in flight.
float sum_neon(const float *a, size_t n) {
float32x4_t v0 = vdupq_n_f32(0), v1 = v0, v2 = v0, v3 = v0;
size_t i = 0;
for (; i + 16 <= n; i += 16) {
v0 = vaddq_f32(v0, vld1q_f32(a + i));
v1 = vaddq_f32(v1, vld1q_f32(a + i + 4));
v2 = vaddq_f32(v2, vld1q_f32(a + i + 8));
v3 = vaddq_f32(v3, vld1q_f32(a + i + 12));
}
float s = vaddvq_f32(vaddq_f32(vaddq_f32(v0, v1), vaddq_f32(v2, v3)));
for (; i < n; i++) s += a[i];
return s;
}
#include <immintrin.h>
#include <stddef.h>
// AVX2: 8 floats per vector. Not executed for this post (no x86 machine).
float sum_avx2(const float *a, size_t n) {
__m256 v0 = _mm256_setzero_ps(), v1 = _mm256_setzero_ps();
size_t i = 0;
for (; i + 16 <= n; i += 16) {
v0 = _mm256_add_ps(v0, _mm256_loadu_ps(a + i));
v1 = _mm256_add_ps(v1, _mm256_loadu_ps(a + i + 8));
}
__m256 v = _mm256_add_ps(v0, v1);
__m128 lo = _mm256_castps256_ps128(v), hi = _mm256_extractf128_ps(v, 1);
__m128 s4 = _mm_add_ps(lo, hi);
s4 = _mm_hadd_ps(s4, s4); s4 = _mm_hadd_ps(s4, s4);
float s = _mm_cvtss_f32(s4);
for (; i < n; i++) s += a[i];
return s;
}
A note on correctness: floating-point addition is not associative, so the multi-accumulator versions can give slightly different results from the scalar one. Compilers will not make that change on their own unless you allow it (for example with -ffast-math or -fassociative-math), which is why hand-written or explicitly permitted reductions are common in numeric libraries.
SMT, multicore, NUMA, chiplets and power
Simultaneous multithreading
A single thread rarely uses every port every cycle. SMT (Intel's brand is Hyper-Threading) runs two hardware threads on one core, each with its own architectural registers and program counter, sharing the execution resources. AMD's GDC slide on Zen 4 resource sharing sorts structures into three groups: competitively shared, watermarked and statically partitioned. It places the integer and floating-point schedulers and register files, the load queue and the memory request buffers in the watermarked group, and the op queue, store queue and retire queue in the statically partitioned group. Intel's manual says that with SMT active the Golden Cove IDQ holds 72 micro-ops per thread instead of 144, and, describing Hyper-Threading in general, that several buffers are shared by limiting each logical processor to half the entries.
SMT helps when threads stall on memory or mispredictions; it can hurt when one thread already saturates a shared resource. It also shares caches and predictors between threads, which is relevant to side channels.
Multicore and NUMA
Many cores on a chip share the last level cache and memory controllers through an on-chip interconnect. In a multi-socket server, or a large single-socket part with several memory controllers, memory attached to one node is faster for that node's cores. That is non-uniform memory access (NUMA). The Linux kernel's NUMA documentation describes how the kernel groups CPUs and memory into nodes and tries to allocate memory local to the CPU that touches it.
Useful commands on Linux:
lscpu # sockets, cores, threads, NUMA nodes, cache sizes, flags
numactl --hardware # node distances and memory per node
numactl --cpunodebind=0 --membind=0 ./app # pin a process to node 0
cat /sys/devices/system/cpu/cpu0/cache/index*/size # per-level cache sizes
Chiplets
Instead of one large die, a chiplet design builds a processor from several smaller dies in one package. AMD's GDC deck shows the desktop Raphael (Ryzen 7000) layout with two core complex dies (CCDs), each with its own 32 MB L3, next to an I/O die. Smaller dies yield better on a wafer, and the I/O die can use a different process than the cores. The cost is that a core reading a line held in the other CCD's L3 must cross the package, so "L3 hit" is no longer one latency. Our post on how a GPU is made covers the packaging side in more depth.
Power and boost
Dynamic power in CMOS logic is roughly proportional to activity × capacitance × voltage squared × frequency. Higher frequency needs higher voltage, so power rises faster than linearly with clock speed. Worked example: cut both voltage and frequency by 10% and dynamic power falls to 0.9 × 0.9 × 0.9 = 0.729 of the original, a 27% saving for a 10% clock reduction.
That is why CPUs boost: they raise frequency and voltage when power, current and temperature limits allow, often on only a few cores, and drop back when all cores are busy. The Linux kernel's CPU performance scaling documentation (cpufreq) describes how the OS and the hardware share that control, including frequency boost support. The practical consequence for anyone measuring performance: a benchmark that runs on one core at a boost clock tells you little about all-core throughput, and results drift with temperature.
How do speculative execution side channels work?
Everything above relies on speculation: predicted branches, early loads, and work that may be thrown away. The ISA promises that discarded work has no architectural effect, and it does not; registers and memory roll back. But it does leave microarchitectural traces, most importantly which cache lines were brought in. In 2018 researchers showed that those traces can be measured.
Google Project Zero says it reported the issues to Intel, AMD and Arm on 1 June 2017, and the work became public in January 2018. Project Zero's post lists three variants: variant 1, bounds check bypass (CVE-2017-5753); variant 2, branch target injection (CVE-2017-5715); and variant 3, rogue data cache load (CVE-2017-5754), the issue the Meltdown paper describes. The Linux kernel's Spectre documentation covers the first two.
At concept level only:
- Spectre (Kocher et al.) abuses prediction. A victim program has a bounds check followed by a dependent load. If the predictor has been trained to expect "in bounds", the CPU may speculatively run the load with an out-of-bounds index and use the loaded value to touch a cache line. The misprediction is caught and rolled back, but the cache footprint remains, and timing a later access reveals it. Variant 2 does the same by steering an indirect branch predictor toward code the attacker chose.
- Meltdown (Lipp et al.) abused the fact that, on affected processors, a load from kernel memory issued from user mode could forward data to dependent instructions before the permission check raised the fault. As the paper's title puts it, this allowed "reading kernel memory from user space".
The fixes illustrate how many layers of the stack a hardware issue touches:
| Layer | Example mitigation |
|---|---|
| Microcode | New controls over indirect branch prediction delivered as microcode updates |
| Operating system | Kernel page table isolation for Meltdown; flushing predictor state on context switches |
| Compiler | Retpolines for indirect branches; speculation barriers after bounds checks |
| Applications and browsers | Site isolation, reduced timer precision, process separation |
| Hardware | Later designs fix or restrict the behaviour in silicon |
On Linux you can see what the kernel believes about the CPU and which mitigations are active:
grep . /sys/devices/system/cpu/vulnerabilities/*
# e.g. /sys/devices/system/cpu/vulnerabilities/spectre_v1:Mitigation: ...
We are deliberately not describing attack steps. The point for engineers is structural: speculation is a performance feature with a security cost, and the mitigations ship as software, microcode and firmware that must be kept current.
Worked example: tracing a loop through the pipeline
Let's trace the sum_scalar loop from the SIMD section through a core, then estimate how the faster versions change it. We use Arm's Neoverse N2 numbers for floating-point add because the guide publishes both latency and throughput: FADD has latency 2 cycles and throughput 2 per cycle on the V pipes. We assume the array is already in L1 and ignore loop overhead where it does not matter.
An AArch64 compiler turns the scalar loop into something like this (typical shape, not a specific compiler's output):
loop:
ldr s1, [x0], #4 // load a[i], post-increment pointer
fadd s0, s0, s1 // s += a[i]
subs x1, x1, #1 // n--
b.ne loop // branch if n != 0
Step by step through the stages
- Predict and fetch. The branch predictor has seen
b.ne looptaken many times, so the BTB redirects fetch back toloopevery iteration without waiting. Four 32-bit instructions are 16 bytes, so one fetch block covers many iterations. - Decode. Each instruction becomes one or two operations. The load with post-increment writes two registers (s1 and x0), so it may split into a load operation and an address update.
- Rename. Every iteration writes s1, s0, x0 and x1 again. Rename gives each new value a fresh physical register, so iteration 5's load does not wait for iteration 4's add to read s1. The only chain left is the one through s0, which is a true data dependency.
- Dispatch. N2 can dispatch up to 5 macro-ops per cycle, so a whole iteration fits in one cycle's dispatch bandwidth.
- Issue and execute. Loads issue as soon as their address is ready (the pointer chain is a cheap 1-cycle add per iteration), so loads for iterations far ahead complete early. The FADDs cannot: each one needs the previous sum.
- Retire. Iterations retire in order as their FADD completes.
Here is the steady state. Loads run ahead; the FADD chain sets the pace.
cycle: 0 1 2 3 4 5 6 7 8
ldr i0 X
ldr i1 X
ldr i2 X
ldr i3 X
fadd i0 [====] (latency 2)
fadd i1 [====] waits for fadd i0
fadd i2 [====] waits for fadd i1
fadd i3 [====] ...
One FADD finishes every 2 cycles, so the loop runs at 0.5 elements per cycle, while the hardware could start two FADDs every cycle. The machine is latency bound: three quarters of the FADD capacity is idle.
What each version of the loop buys
| Version | Operations for 1,024 floats | Bound | Cycle estimate for the adds |
|---|---|---|---|
| Scalar, one accumulator | 1,024 FADD | Latency: 1,024 × 2 | 2,048 |
| Scalar, four accumulators | 1,024 FADD in 4 chains | Throughput: 1,024 ÷ 2; each chain is 256 × 2 = 512 | 512 |
| NEON 128-bit, four vector accumulators | 256 vector FADD | Throughput: 256 ÷ 2 | 128 |
The accumulator count is not arbitrary. To keep a pipe busy you need latency × throughput operations in flight: 2 cycles × 2 per cycle = 4 independent chains. With four accumulators both limits meet at 512 cycles. Adding SIMD on top divides the operation count by four lanes, giving 128 cycles, a 16× estimate over the original 2,048. The 256 vector loads (16 bytes each) have three load-capable pipes on N2, so they would need at least about 85 issue cycles, under the 128-cycle FADD bound, which is why the adds and not the loads set the pace while the data stays in L1.
These are model estimates from published latency and throughput, not measurements. Real loops add loop overhead, alignment, a tail and the final horizontal sum, and once the array no longer fits in L1 the memory hierarchy, not the adder, becomes the limit. For a 1 GB array the question changes completely: it is DRAM bandwidth per core, and no amount of instruction-level cleverness gets past it. That is the same roofline reasoning used for GPUs, applied to a CPU.
What a branch misprediction does to this loop
At the final iteration the predictor guesses "taken" once more and is wrong. The core has already fetched, renamed and maybe executed instructions from a phantom extra iteration. When b.ne resolves, everything younger than it in the ROB is discarded, the RAT is restored, and fetch restarts at the instruction after the loop. For a 1,024-iteration loop, one misprediction of perhaps 15 cycles (our assumed penalty) is negligible. For a short loop with a data-dependent trip count called millions of times, it is not.
Why CPU microcode and firmware are a supply chain concern
Everything in this post runs on hardware, but a surprising amount of it is set or patched by software you install:
- Microcode updates change how the CPU behaves after it ships, including mitigations for issues like the ones above. Intel publishes its Linux microcode files in a public GitHub repository with dated release tags; the newest one at the time of writing is microcode-20260925, published 25 September 2026.
- Linux loads microcode either early, from a CPIO archive prepended to the initrd (the kernel documentation shows the paths
kernel/x86/microcode/GenuineIntel.binandkernel/x86/microcode/AuthenticAMD.bin), or late through a sysfs reload. The kernel docs note that since kernel 5.19 late loading is not enabled by default and explain why it is risky on a running system. - Platform firmware (UEFI, the BIOS that also carries microcode) is a separate update path controlled by the board or system vendor.
- Software that adapts to the CPU (compilers, math libraries, runtimes, cryptography libraries) contains CPU-specific code paths chosen at run time. A vulnerable version of one of those libraries is an ordinary dependency problem.
Checking the microcode and mitigation state of a Linux host:
grep -m1 microcode /proc/cpuinfo # current microcode revision
journalctl -k | grep -i microcode # what the kernel loaded at boot
grep . /sys/devices/system/cpu/vulnerabilities/* # mitigation status per issue
rpm -q microcode_ctl || dpkg -l intel-microcode amd64-microcode # distro package version
Our post on semiconductor fab machines, firmware and SBOMs covers the firmware inventory problem from the manufacturing side.
Common mistakes
- Treating the ISA as the performance model. Two x86-64 CPUs run the same binary with very different decode width, cache sizes and SIMD support. Tune for the microarchitecture you deploy on, and check its vendor manual.
- Quoting reorder buffer or cache sizes from reviews as vendor facts. If the vendor manual does not state a number, say so. This post leaves several cells blank for exactly that reason.
- Assuming AVX-512 is present because the CPU is "new". Intel's own manual notes that Alder Lake E-cores do not support AVX-512 and some 512-bit features are absent on client parts. Dispatch on runtime feature checks.
- Optimizing instruction count instead of dependency chains. The worked loop shows a 4× gain with the same number of additions, purely by breaking one chain into four.
- Ignoring false sharing. Per-thread counters in one 64-byte line can make a parallel loop slower than the serial one.
- Benchmarking at boost clocks only. Single-core boost behaviour does not predict sustained all-core throughput, and thermal state changes results run to run.
- Leaving 4 KB pages on huge working sets. Intel's Ice Lake STLB reaches 8 MB with 4 KB pages and 2 GB with 2 MB pages. Large in-memory databases notice the difference.
- Forgetting microcode in patch plans. Kernel and library updates are tracked; microcode and BIOS updates often are not, even though some speculative execution mitigations depend on them.
- Enabling fast-math globally to get vectorized reductions. It changes floating-point semantics everywhere in that translation unit. Prefer explicit reductions or narrowly scoped flags.
FAQ
How does a CPU execute an instruction, step by step?
The branch predictor picks the next fetch address, the fetch unit reads bytes from the instruction cache, the decoder turns them into internal micro-operations, rename maps registers to physical registers and allocates a reorder buffer entry, a scheduler issues the operation to an execution port when its inputs are ready, and the retire stage commits the result in program order.
What is the difference between an ISA and a microarchitecture?
The ISA (x86-64, Arm A64, RISC-V) is the specification of instructions, registers and memory behaviour that software relies on. A microarchitecture (Golden Cove, Zen 4, Neoverse N2) is one hardware design that implements an ISA, with its own pipeline width, buffers and caches.
What does out-of-order execution actually do?
It lets the core execute an instruction as soon as its inputs are ready, even if older instructions are still waiting, typically on a cache miss. A reorder buffer then retires results in the original order, so software sees the same outcome as strict in-order execution.
What is a micro-op cache?
It is a cache of already-decoded operations, keyed by instruction address. On x86 it avoids the cost of decoding variable-length instructions again. Intel says Golden Cove's micro-op cache delivers up to 8 micro-ops per cycle, and AMD's Zen 4 op cache holds 6.75K ops and delivers 9 macro-ops per cycle.
How accurate are branch predictors, and what is TAGE?
Vendors do not publish accuracy figures in the manuals we used. TAGE, from Seznec and Michaud's 2006 paper, combines a base predictor with several tagged tables indexed by global histories of geometrically increasing length, then uses the longest matching history. It is the reference design for modern research.
Why do CPUs have L1, L2 and L3 caches?
Because no single memory can be both large and fast. Intel lists 5 cycles for the Ice Lake client L1 data cache and 13 for L2; DRAM takes hundreds of cycles. Layered caches keep the most-used data close and catch most misses before they reach memory.
What is the difference between MESI and MOESI?
Both keep private caches coherent. MESI has Modified, Exclusive, Shared and Invalid states. MOESI, documented by AMD, adds Owned, which lets a dirty line be shared with other caches without first writing it back to memory.
What is SIMD and how is AVX-512 different from NEON or SVE?
SIMD runs one instruction across several data lanes. AVX-512 uses fixed 512-bit registers (16 single-precision floats). NEON uses fixed 128-bit registers (4 floats). SVE lets each implementation choose a width from 128 to 2048 bits and lets the same binary run on any of them.
Do Spectre and Meltdown still matter?
Yes, as a class. The original variants are mitigated through microcode, kernel, compiler and browser changes, and the Linux kernel reports mitigation status under /sys/devices/system/cpu/vulnerabilities. Keeping microcode, firmware and kernels current is part of normal patching.
How do I check which microcode my Linux server is running?
Run grep -m1 microcode /proc/cpuinfo for the current revision and journalctl -k | grep -i microcode for what was loaded at boot. Compare against your distribution's microcode package and, for Intel, the release tags in Intel's public microcode repository.
Where Safeguard fits
Safeguard does not design, program or speed up CPUs, and it does not scan CPU microcode or platform firmware. Microcode and BIOS updates are a host and fleet management job, tracked with the operating system and vendor tools shown above. What Safeguard does cover is the software layer that sits on top: it generates SBOMs (SPDX and CycloneDX) for the repositories and container images you build, matches every component against vulnerability data with EPSS and CISA KEV enrichment, uses reachability analysis to rank the vulnerable code paths your application actually calls, and can open tested fix pull requests for dependencies. That covers the compilers' runtime libraries, numeric and cryptography libraries and language runtimes you ship, which are the CPU-adjacent components that most often appear in advisories against application stacks.
A CPU does not live alone: compilers, math and cryptography libraries, language runtimes and container base images all carry open source components that decide which instructions actually run, and those components get advisories like any other dependency. Microcode and BIOS updates stay with your hardware and OS vendors, but the application layer above them is where the following steps apply.
More in this series
Other posts in our GPUs, CPUs, QPUs and lithography series:
- AMD GPU Architecture Explained: GCN, RDNA, CDNA, Infinity Fabric and HIP
- CPU vs GPU vs QPU Explained: How Each Works and Why They Coexist
- CUDA Programming Model Explained: Grids, Blocks, Warps and Memory
- GPU Memory and Interconnects: HBM, GDDR, NVLink, PCIe, CXL
- CUDA vs HIP vs SYCL vs OpenCL vs Metal vs Vulkan vs Triton Compared
- GPU Software Supply Chain: Drivers, CUDA, cuDNN, NCCL and Your SBOM
- How a QPU Works: Qubit Chips, Fridges, Control Pulses and Readout
- How EUV Lithography Machines Work: Tin Plasma, Mirrors and High-NA
- How GPUs Work: Graphics Pipeline, Shader Cores, Ray Tracing and Compute
- Inside an NVIDIA SM: Warp Schedulers, Registers, Tensor Cores
- Intel GPU Architecture: Xe Cores, XMX, Ponte Vecchio and oneAPI SYCL
- NVIDIA GPU Architecture History: From Tesla and Fermi to Blackwell
- NVIDIA vs AMD vs Intel vs Apple GPUs: Architectures Compared
- Optimizing a CUDA Matrix Multiply Step by Step, From Naive to Tensor Cores
- Photolithography Explained: Contact Printing to DUV Immersion and Overlay
- Tile-Based GPUs Explained: Apple, Mali, Adreno, PowerVR and TBDR
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
These demos show dependency, image and remediation workflows. They do not touch CPU microcode, firmware or performance. You can download Safeguard and follow the CLI docs to try them on your own projects.
This demo sets up the Safeguard runner on your own machine, approves it with its device code and scans from the terminal. Pointed at a container image for a numeric or HPC service, the same scan lists the packages and versions inside it, including the math, compression and cryptography libraries that carry CPU-specific code paths, along with their known vulnerabilities.
Click through it at your own pace, or open it full screen.
This demo is a real CLI session: ask it to fix the lodash CVE in a repository and watch it read the finding and the advisory, ask before any command that changes something, move lodash to 4.17.21, run npm install and npm test with your approval, and push nothing. The same flow applies when a library you depend on, such as a SIMD-accelerated parser or hashing package, publishes a fixed version.
Click through it at your own pace, or open it full screen.
This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. You can then ask which of your projects ship a given package version, for example a specific release of a linear algebra or crypto library, and get the answer from your own scan data rather than from memory.
Click through it at your own pace, or open it full screen.
Quick check
A loop sums floats into one variable on a core whose FP add has latency 2 and throughput 2 per cycle. How many independent accumulators do you need to keep the adders busy, and why?
Four. The number of operations that must be in flight is latency × throughput, here 2 cycles × 2 per cycle. With one accumulator, each add waits for the previous one, so only one add completes every 2 cycles, a quarter of the available rate.
A 32 KB, 8-way L1 data cache uses 64-byte lines. Why can it be indexed with the virtual address before the TLB finishes?
It has 64 sets, so it needs 6 index bits plus 6 offset bits, 12 bits in total. Those 12 bits fall inside the 4 KB page offset, which is identical in the virtual and physical address. The cache can pick the set immediately and compare physical tags once translation completes.
Why did Spectre need fixes in microcode, operating systems, compilers and browsers rather than in one place?
Because the leak comes from speculative work leaving cache traces, which can be triggered from many directions: branch predictor state (microcode and OS controls), kernel memory mappings (OS page table isolation), code patterns such as bounds checks and indirect branches (compiler barriers and retpolines), and untrusted code in the same process (browser site isolation and timer changes).
Sources
- Intel: Optimization Reference Manual, documentation changes 355308-002 with revised text of manual 248966-048 (August 2023)
- Intel: Optimization Reference Manual, documentation changes 355308-051 (October 2026)
- Arm: Neoverse N2 Software Optimization Guide, Issue 5.0 (31 October 2022)
- AMD: Ryzen Processor Software Optimization, GDC 2024 (March 2024)
- AMD: AMD64 Architecture Programmer's Manual, Volumes 1 to 5
- Seznec and Michaud: A case for (partially) TAgged GEometric history length branch prediction, JILP Vol. 8 (February 2006)
- Jiménez and Lin: Dynamic Branch Prediction with Perceptrons, HPCA 2001
- Stephens et al.: The ARM Scalable Vector Extension, arXiv:1803.06185
- Kocher et al.: Spectre Attacks: Exploiting Speculative Execution
- Lipp et al.: Meltdown: Reading Kernel Memory from User Space
- Google Project Zero: Reading privileged memory with a side-channel (January 2018)
- Linux kernel documentation: Spectre side channels
- Linux kernel documentation: Hardware vulnerabilities
- Linux kernel documentation: The Linux microcode loader
- Linux kernel documentation: x86-64 memory management (4-level and 5-level page tables)
- Linux kernel documentation: What is NUMA?
- Linux kernel documentation: CPU performance scaling (cpufreq)
- Intel: Linux Processor Microcode Data Files (GitHub)
- RISC-V International: Ratified specifications
- RISC-V ISA manual releases (GitHub)
- Hennessy and Patterson, Computer Architecture: A Quantitative Approach (book, cited by title)
- Papamarcos and Patel, "A Low-Overhead Coherence Solution for Multiprocessors with Private Cache Memories", ISCA 1984 (paper, cited by title)
Last reviewed: 25 October 2026.