Foundationsexecutionmemory hierarchycostmental modelorientation

What Actually Happens When You Add Two Numbers

One line of source becomes a handful of instructions, and the addition itself is the cheapest thing in it. The expensive question — the one this entire domain exists to answer — is where a and b were when the CPU went looking for them.

▶ Run the labFollow the mechanism

Software view, hardware view

The gap between what you wrote and what the machine does is where this whole domain lives.

The question
When I write `int x = a + b;`, what does the machine actually do — and where does the time actually go?
What you wrote
One statement. One addition. It either happens or it does not, and it happens fast enough that thinking about it feels like premature optimisation.
What the hardware does
The compiler emits several instructions. The CPU fetches them, decodes them, reads two registers, drives an adder, and writes a register back. The addition occupies a single cycle in an execution unit that could do several per cycle. Getting `a` and `b` *into* those registers is the part that can cost hundreds of times more, and nothing in the source line tells you which case you are in.
Almost every performance surprise in this domain follows from that asymmetry. Arithmetic is abundant and cheap; data movement is scarce and expensive. A mental model that counts operations will mispredict real programs, because real programs are usually waiting for data rather than computing on it.
SourceCompilerInstructionsFront EndExecutionRegistersCachesMemoryI/OBehavior

One line, and the machine's answer to it

Source code is a description of intent. The machine does not execute it; it executes instructions the compiler produced from it, and those instructions operate on registers — a few dozen named slots physically inside the CPU. Nothing arithmetic happens to memory directly on most architectures: values must be loaded into registers, operated on, and stored back.

So int x = a + b; becomes roughly: load a, load b, add them, store the result. The exact instructions depend on where the compiler decided those variables live, which depends on register pressure, optimisation level and the calling convention. If a and b were already in registers — because they were just computed, or the loop keeps them there — the loads vanish entirely and only the add remains.

The path below is the loop this whole domain follows. Each arrow is a lesson: Instruction Fetch: Code Is Data Too gets the bytes, Decode: Turning Bytes Into Intent works out what they mean, Registers: The Fastest Storage, and There Is Almost None of It supplies the operands, The ALU: Where Arithmetic Actually Happens does the arithmetic, and the result becomes architecturally real at The Reorder Buffer and Precise State. What the source line shows you is the middle of that chain and none of its cost.

int x = a + b;CompilerMachine instructionsFetchDecodeRegister readALU: addRegister write
UserLLMAgentToolDataDecisionHumanGuardrail

The second question: where were `a` and `b`?

Now ask the question the source line cannot answer. A load instruction says "fetch the value at this address". It does not say how long that takes, because that depends entirely on where the value currently sits — and the same instruction, in the same program, can resolve in a single-digit number of cycles or in several hundred depending on nothing more than what ran before it.

The scale below is deliberately unitless. Published nanosecond figures are wrong on every machine except the one they were measured on, and they age badly; what survives the move from one CPU to another is the *ratio*. The shape — registers are essentially free, each cache level costs several times the last, main memory costs a couple of orders of magnitude more than L1 — is stable across decades of hardware even as every absolute number changes.

Read the ratio, not the row. A cache hit and a DRAM access differ by roughly two orders of magnitude, which means a loop that misses every iteration can be a hundred times slower than the identical loop over data that fits in cache. That single fact explains more real performance mysteries than any other in this domain, and The Memory Hierarchy is where it gets developed properly.

Relative cost of obtaining one operand, by where it currently lives — 1 unit ≈ one register readSIMPLIFIED
Register×1
L1 cache hit×4
L2 cache hit×12
L3 cache hit×40
Main memory (DRAM)×200
Ratios, not times. Absolute latencies depend on the processor, its clock, the memory it is attached to and what else is running — publishing them would be wrong everywhere except one machine. The bars are log-scaled, so each step is larger than it looks.
RegisterAlready inside the CPU. This is the case the compiler works hard to arrange.
L1 cache hitSmall and close; the common case for well-behaved code.
L2 cache hitLarger, further away.
L3 cache hitUsually shared between cores, which makes it slower and more contended.
Main memory (DRAM)Off-chip. Two orders of magnitude away from a register, and the exact ratio varies widely by platform.

What this domain is actually about

The table below is the set of beliefs this domain exists to correct. None of them are stupid — each is a reasonable extrapolation from how source code reads. They are simply not how the machine behaves, and every one of them leads to a specific class of wrong prediction about performance.

Notice the shape of the corrections. Source code is sequential and the machine is not (Out-of-Order Execution). Source code treats memory as flat and the machine does not (The Memory Hierarchy). Source code makes arithmetic and memory access look equally cheap, and they differ by two orders of magnitude. Each gap is a place where reasoning from the source alone produces confident, wrong answers — which is exactly what Why Reading the Source Cannot Tell You the Cost is about.

The goal is not to memorise CPU terminology. It is to be able to look at a line of code and ask a better set of questions: where is this data, has anything touched it recently, can these operations overlap, and what will the machine do when it cannot tell what comes next.

The mental model you arrive with, and the one this domain replaces it with
Reasonable beliefWhat the machine actually doesWhere it is developed
Instructions execute one at a time, in orderMany are in flight at once, executed as their inputs become ready and retired in program orderOut-of-Order Execution
Memory access takes a fixed amount of timeCost spans about two orders of magnitude depending on which level holds the dataThe Memory Hierarchy
Adding is work; reading a variable is freeThe add is nearly free; the read is what you pay for when it missesWhat a Cache Actually Is
The CPU knows which branch it will takeIt guesses, runs ahead speculatively, and discards the work when wrongBranch Prediction: Guessing Well Enough to Matter
Source order is execution orderThe compiler reorders, then the hardware reorders again within permitted limitsThe Compiler Reordered It Before the CPU Did, Why Your Loads and Stores Happen Out of Order
More clock speed means proportionally more work doneWork per cycle varies enormously with stalls, and clock is only one factorThe Clock: Why GHz Is Not Performance, IPC: Instructions Per Cycle

Key points

  • The arithmetic in a + b is the cheapest part; obtaining the operands is where the cost lives.
  • Arithmetic happens between registers — values must be loaded in and stored back on most architectures.
  • The cost of a load spans roughly two orders of magnitude depending on which level of the hierarchy holds the data.
  • Relative cost transfers between machines; absolute nanosecond figures do not, and go stale quickly.
  • The purpose of this domain is better questions about a line of code, not more terminology.

Progressive depth

Overview

A line of arithmetic becomes a few instructions. The arithmetic is cheap. Fetching the operands is what varies, and it varies by roughly a hundredfold depending on where they are.

Practical

Because loads dominate, performance follows data layout and access order rather than operation counts. Sequential access over compact data is fast; scattered access over pointer-linked data is slow, even at identical asymptotic complexity. This is why Both Are O(n). One Is Far Slower. is a real performance question rather than a style preference.

Advanced

The CPU works hard to hide these costs: it prefetches lines it expects to need (Prefetching: The Hardware Guesses What You Will Read Next), keeps many loads in flight simultaneously (Misses That Overlap Are Nearly Free), and executes independent instructions while a load is outstanding (Out-of-Order Execution). These mechanisms succeed on predictable access patterns and fail on unpredictable ones, which is why pointer chasing is slow in a way that raw latency alone does not explain.

Internals

A load that misses everywhere becomes a physical event: address translation through the The MMU: Translation and Protection in One Check and The TLB: A Cache for Addresses, Not Data, a request to the memory controller, DRAM row activation, and a burst transfer of an entire Memory Moves in Lines, Not Variables worth of data — never just the bytes you asked for. The instruction depending on that value cannot retire until it returns, so the reorder buffer fills behind it and issue eventually stalls. That is what a "slow line of code" physically is.

Where the Data Is

Change an input and watch which number moves — and which one refuses to.

Where a value can be, and roughly what each costs relative to a register — 1 unit ≈ one register accessSIMPLIFIED
Register×1
L1 cache×4
L2 cache×14
L3 cache×45
DRAM×200
NVMe storage×100000
Network round trip×10000000
Ratios, not times. Absolute latencies depend on the processor, its clock, the memory it is attached to and what else is running — publishing them would be wrong everywhere except one machine. The bars are log-scaled, so each step is larger than it looks.
RegisterAlready in the core. Effectively free.
L3 cacheUsually shared between cores, so other work affects your hit rate.
DRAMTwo orders of magnitude past L1. This is the cliff.
NVMe storageAnother three orders of magnitude, and the OS gets involved.
Network round tripDifferent universe. Included to keep the earlier rows in perspective.

The exact ratios vary by machine and the absolute times vary far more, which is why none are shown. What is stable enough to build intuition on is the shape: each level is several times the one above, and the gap between the last cache level and memory is the one that decides most program performance.

Follow the mechanism

The path through the machine, hop by hop — and the conclusions it invites that are wrong.

  1. 1
    Source → compiler: int x = a + b; becomes instructions, with the decision about where a and b live made at compile time.
  2. 2
    Program counter → instruction cache: the fetch unit requests the bytes of the next instruction.
  3. 3
    Decode → register file: the instruction names two source registers and one destination.
  4. 4
    Load unit → cache hierarchy: if an operand is not already in a register, a load searches L1, then L2, then L3, then main memory, at sharply rising cost.
  5. 5
    ALU → register file: the addition executes in a single cycle and the result is written back, becoming architecturally visible when the instruction retires.
What people conclude from this — wrongly
  • Concluding that because the algorithm is O(n) either way, the implementations will perform similarly — complexity says nothing about the constant that memory imposes.
  • Assuming the compiler's output resembles the source statement by statement; it frequently does not, and reading the disassembly is the only way to know.
  • Treating one published latency table as universal truth rather than as one measurement of one machine.
  • Concluding that since the addition is fast, the line is fast — the load in front of it is the part that varies.

Consequences, controls and cost

What it causes
  • • Two programs performing identical arithmetic can differ by a large factor purely because of where their data sits.
  • • Optimising arithmetic in a memory-bound loop produces no measurable improvement, which is the most common wasted optimisation in practice.
  • • Performance becomes sensitive to data layout and access order — properties invisible in the algorithm itself.
  • • Reasoning that counts operations will systematically mispredict which of two implementations is faster.
What you can do
  • • Arrange for data to be reused while it is still close: access it in the order it is stored, and finish with it before moving on ([[spatial-locality]], [[temporal-locality]]).
  • • Choose layouts that put the bytes you need together, rather than scattering them across the address space ([[data-oriented-design]]).
  • • Let the compiler keep hot values in registers by keeping loops tight and avoiding unnecessary indirection.
  • • Measure before assuming which half of the problem you have — arithmetic and memory need different fixes ([[cpu-bound-vs-memory-bound]]).
How to see it
  • • Compile with optimisation and read the generated assembly; count the loads, not just the arithmetic.
  • • Compare cycles against instructions retired to get **IPC** — a low value with high instruction counts usually means stalls, not expensive work ([[ipc]]).
  • • Run the same loop over a dataset that fits in cache and one that does not; the ratio is the memory effect isolated from everything else.
  • • Sample cache-miss counters during the hot loop rather than reasoning about them ([[performance-counters]]).
What it costs
  • • Layouts optimised for one access pattern are usually worse for another; there is no universally cache-friendly arrangement.
  • • Restructuring data for locality often costs readability and can hurt maintainability more than it helps performance.
  • • Reasoning at this level is only worth it for code that actually runs hot; applied everywhere it is a large cost for no measurable return.

Scope

§224 — what these claims are specific to.

What these claims are specific to
  • SIMPLIFIEDThe fetch → decode → execute → writeback chain shown here is a teaching model. Real cores split these into many more stages, run several instructions per cycle, and reorder aggressively — see Out-of-Order Execution.
  • GENERALThe register-to-register arithmetic model holds for common load-store architectures such as AArch64 and RISC-V. x86-64 instructions may name a memory operand directly, but internally the value is still loaded before the ALU sees it.
  • PLATFORM-SPECIFICEvery relative figure in the cost scale varies by CPU, generation and memory configuration. The ordering and rough magnitudes transfer; the numbers do not.

Misconceptions

Claim
“Memory access takes a fixed, known amount of time.”
Reality
The same load instruction can complete in a few cycles or several hundred depending only on which level of the hierarchy currently holds the line. Nothing in the source distinguishes those cases.
Claim
“If two implementations are both O(n), they will perform about the same.”
Reality
Asymptotic complexity deliberately discards the constant, and on real hardware that constant is dominated by memory behaviour. An O(n) traversal of contiguous memory and an O(n) traversal of scattered pointers routinely differ by a large factor — see Both Are O(n). One Is Far Slower..
Claim
“The CPU executes my source code.”
Reality
It executes instructions a compiler generated, which may be reordered, merged, vectorised or eliminated relative to what you wrote — and then the hardware reorders them again. See The Compiler Reordered It Before the CPU Did.

Apply it

Where the rest of this lives

Programming Languages & Runtime Internals
Compilation and code generation

Which instructions the compiler emits — and whether a and b ever reach memory at all — is a code-generation decision made before the CPU is involved. That domain does not exist yet; for now, read the disassembly.