Learn Computer Architecture

Start from what you wrote and follow it down: instructions, the front end, execution units, registers, caches, memory. Every lesson names what its claims are specific to, because almost nothing here is true of every machine.

Digital Foundations →

How arithmetic emerges from logic: binary and two's complement, why floating point approximates, gates, adders, and the step from combinational logic to stored state.

What Actually Happens When You Add Two Numbers
▶ lab

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.

Q · When I write `int x = a + b;`, what does the machine actually do — and where does the time actually go?
Binary, and Why Everything Is Eventually Bits
▶ lab

Binary is not a numbering curiosity you convert for exam questions. It is the substrate: instructions, addresses, permission flags, protocol headers and floating-point values are all bit patterns, and hexadecimal exists because humans cannot read them otherwise.

Q · Why does everything in computing eventually come down to binary, and what does that actually change about how I read a value?
Bits, Bytes and Words — and Why "Word" Is Not a Fixed Size

The byte is nearly universal. The word is not: it means whatever a given architecture, compiler or document says it means, and the confusion this causes is responsible for a surprising share of portability bugs.

Q · What is a "word", why does its size keep changing depending on who is talking, and what actually depends on it?
Two's Complement: One Circuit for Addition and Subtraction

Negative numbers are not stored with a minus sign. They are stored so that ordinary binary addition produces the right answer without the hardware ever knowing a value was negative — which is why subtraction needs no separate circuit.

Q · How does hardware represent negative numbers, and why does that particular representation make the arithmetic circuitry simpler?
Integer Overflow: The Hardware Wraps, the Language Decides

Add one to the largest 8-bit signed value and the bits roll around to the most negative one. The hardware behaviour is simple and identical everywhere; what your language claims about it ranges from "wraps" to "this can never happen, and I will optimise on that basis".

Q · What happens when an integer exceeds the range its bits can represent — and why do different languages disagree about it so sharply?
Floating Point: Trading Precision for Range
▶ lab

A float is scientific notation in binary: a sign, an exponent that slides the point, and a fraction. That design buys an enormous range from a fixed number of bits, and it pays for it with precision that varies depending on how large the value is.

Q · How does a fixed number of bits represent both very large and very small numbers, and what does that design cost?
Why 0.1 + 0.2 Is Not 0.2 + 0.1's Problem

The famous result is not a bug and not a rounding display quirk. 0.1 has no exact binary representation for the same reason 1/3 has no exact decimal one, and every consequence — failed equality tests, drifting sums, order-dependent results — follows from that single fact.

Q · Why does `0.1 + 0.2` not equal `0.3`, and what should I actually do about it?
Boolean Logic: The Four Operations Hardware Actually Has

AND, OR, NOT and XOR are not programming conveniences layered over arithmetic. They are the primitive operations from which arithmetic itself is constructed, and they remain the cheapest instructions a CPU offers.

Q · Which logical operations does hardware actually implement, and why do the same four keep appearing everywhere above it?
Logic Gates: Where Software Stops and Physics Starts
▶ lab

A gate is a few transistors that compute one Boolean function of its inputs. Everything above — arithmetic, memory, control, the entire machine — is gates composed with other gates, and the two properties that matter are that composition is universal and that propagation takes time.

Q · What is physically doing the computing, and what constrains how fast it can go?
Building an Adder: Where Arithmetic Comes From

XOR gives you the sum bit, AND gives you the carry, and that is a half adder. Chain them and you can add any width — but the carry has to travel through every stage in turn, and that dependency is the reason adder design is a real engineering problem.

Q · How does arithmetic emerge from logic gates, and what limits how fast an addition can be?
Adding Memory: Combinational, Sequential and the Clock

Combinational logic has no memory — the output is a function of the inputs right now. Add feedback and you can store a bit; add a clock and you can control when stored values change. That step is what turns a calculator into a machine that executes programs.

Q · How does hardware remember anything, given that gates just compute a function of their current inputs?
Inside a CPU →

The parts that execute an instruction: registers as the fastest storage you have, the ALU, the datapath they sit on, the control unit that steers it, and why clock speed is not performance.

What Is Actually Inside a CPU

A CPU is not one thing that runs instructions. It is a front end that fetches and decodes them, a set of execution units that do the work, a register file they read and write, and caches feeding all of it — with most of the silicon spent on keeping those units busy rather than on the arithmetic itself.

Q · What are the actual parts of a CPU, and which of them does my code interact with?
Registers: The Fastest Storage, and There Is Almost None of It
▶ lab

Registers are the only storage the ALU can read directly, they are the fastest thing in the machine by a wide margin, and an ISA typically exposes a couple of dozen of them. Everything a compiler does with local variables is an attempt to keep the right values in that tiny space.

Q · Where do the values my arithmetic operates on actually live, and what happens when there is not enough room for them?
The ALU: Where Arithmetic Actually Happens

The arithmetic logic unit performs the operations that source-level arithmetic compiles into, sets the flags that comparisons and branches depend on, and — crucially — does not treat all arithmetic as equal. Add and XOR are nearly free; divide is not.

Q · Which operations does the arithmetic hardware actually perform, and do they all cost the same?
The Control Unit: Turning Instructions Into Actions

Something has to read a decoded instruction and tell the rest of the core what to do with it — which register file ports to open, which ALU operation to select, whether to write memory. That something is the control unit, and it is the least visible and most quietly consequential block in the machine.

Q · What actually converts a decoded instruction into the specific actions the rest of the CPU performs?
The Datapath: How Values Move Through the Machine
▶ lab

Registers feed the ALU, the ALU feeds registers back, and a separate route runs from the register file through address calculation to the data cache and back. That loop, plus the memory path hanging off it, is the datapath — and its shape explains why instruction sets look the way they do.

Q · What is the physical route a value takes from a register, through computation, and back to storage?
The Program Counter: Deciding What Happens Next

One register holds the address of the next instruction. Incrementing it is trivial; redirecting it is the single most disruptive thing that can happen to a modern CPU, because everything the front end fetched behind the redirect turns out to have been the wrong guess.

Q · How does the CPU know which instruction to run next, and why is changing that answer expensive?
The Clock: Why GHz Is Not Performance

A clock cycle is the machine's unit of time, and clock rate is one of three factors in how long a program takes — the other two being how many instructions it runs and how many cycles each takes. Comparing CPUs by gigahertz alone ignores two thirds of the equation and all of the memory system.

Q · What does a clock cycle actually represent, and why does a higher clock rate not reliably mean a faster machine?
Instruction Set Architecture →

The contract between software and hardware — and the distinction that most performance arguments get wrong: the same ISA is implemented by wildly different microarchitectures.

The ISA: The Contract Between Software and Hardware

An instruction set architecture is the specification a compiler targets and a processor promises to honour: which instructions exist, which registers they name, how memory behaves, and how addresses are formed. Everything about how that promise is kept is deliberately outside it.

Q · What exactly is an instruction set architecture, and where does its authority stop?
ISA vs Microarchitecture: The Distinction Everything Depends On

The instruction set is a specification; the microarchitecture is one machine that implements it. Confusing the two produces most of the bad arguments about processor performance, because it attributes to a published contract things that are properties of a particular chip.

Q · When someone says an architecture is fast or slow, which of the two things they might mean is actually responsible?
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.

Q · What actually distinguishes the major instruction sets, and does the choice determine anything a programmer will notice?
RISC vs CISC: A Real Argument That Stopped Predicting Anything

The distinction described a genuine design disagreement about where complexity should live, and it mattered. Then implementations converged: complex instruction sets began decoding into simple internal operations, and reduced ones grew complex instructions. The labels survived the situation that gave them meaning.

Q · What did the RISC/CISC distinction originally mean, and why does it no longer predict how a processor performs?
Reading Assembly Without Writing It

Assembly is the readable form of what the machine will actually execute. Most engineers will never write it, and being able to read it is the single most direct way to check what the compiler did rather than guessing.

Q · What do assembly instructions actually mean, and how do I read them well enough to check what my compiler produced?
Addressing Modes: How an Index Becomes an Address

Instructions do not just name registers — they name ways of computing an address from registers, constants and a scale factor. The reason `arr[i]` costs one instruction rather than three is that the hardware performs exactly the arithmetic that array indexing requires.

Q · How does an instruction specify where in memory to read or write, and why does array indexing come out so cheap?
Executing Instructions →

Fetch, decode, execute, write back — then the pipeline that overlaps them, the hazards that break the overlap, and the forwarding and stalls that patch it up.

Fetch, Decode, Execute — and Why That Story Is Incomplete

Every introduction to CPUs teaches a tidy four-step cycle. It is genuinely useful and it has not described a shipping processor since the early 1990s. Both halves of that sentence matter: learn the model, then learn precisely which parts of it modern hardware abandoned.

Q · What are the steps a CPU takes to run a single instruction — and how much does that tidy four-step story still describe a modern processor?
Instruction Fetch: Code Is Data Too

Before a CPU can do anything with an instruction it has to load it from memory, through a cache, at an address it may have had to guess. The front end is a supply chain, and a starved front end leaves the most sophisticated execution engine in the world with nothing to do.

Q · Where do instruction bytes come from, and what happens to a program when the CPU cannot get them fast enough?
Decode: Turning Bytes Into Intent

A fetched block is just bytes. Decode is where the CPU works out where one instruction ends and the next begins, what operation is requested, and which registers it touches — and how hard that is depends enormously on the instruction encoding the ISA chose.

Q · How does a CPU turn a block of undifferentiated bytes into an operation with operands — and why is that harder on some architectures than others?
Execute: Not All Operations Cost the Same

The execute phase is where the work happens, in a set of specialised functional units. Two things surprise people: different operations take very different numbers of cycles, and an operation's latency and its throughput are separate numbers that can differ by an order of magnitude.

Q · What actually performs the work of an instruction, and why do some operations cost far more than others?
Load and Store: Why Arithmetic Happens in Registers

Almost every ISA makes you bring data into a register before you can compute with it, and write it back explicitly. That looks like bureaucracy until you notice that a load is the one instruction whose cost varies by two orders of magnitude depending on where the data happens to be.

Q · Why do CPUs insist on moving data into registers before operating on it, and what makes a load different from every other instruction?
Pipelining: Throughput Without Making Anything Faster
▶ lab

A pipelined CPU does not execute any single instruction more quickly than an unpipelined one. It overlaps them, so instructions complete more often. Understanding that pipelining buys throughput and not latency explains most of what modern CPUs do and why they do it.

Q · How does overlapping instruction execution increase performance, and what exactly does it improve — and not improve?
Pipeline Hazards: The Three Ways Overlap Fails

Pipelining assumes the next instruction can always start. Three situations break that assumption — a needed value is not ready, the next address is not known, or two instructions want the same hardware — and every hardware performance problem is a variation on one of them.

Q · What prevents a pipeline from starting a new instruction every cycle, and how do the three causes differ?
Forwarding and Stalls: Paying for Dependencies

When one instruction needs another's result, the hardware has two options: route the value directly to where it is needed, or wait. Forwarding covers most cases at no cost. The case it cannot cover — a load feeding the very next instruction — is the shape of every serious memory performance problem.

Q · When an instruction depends on the one before it, what does the hardware do — and when does that dependency actually cost time?
Branches & Speculation →

A CPU cannot afford to wait to find out where a branch goes, so it guesses. What that buys, what a misprediction costs, and why unpredictable data hurts more than extra work.

Control Hazards: The CPU Does Not Know Where You Are Going

A pipelined CPU must fetch an instruction every cycle, but at a conditional branch it does not yet know which instruction comes next. Waiting for the answer is unaffordable on a deep pipeline, which is why every high-performance CPU guesses instead.

Q · Why is a conditional branch a problem for a pipelined CPU, and why can it not simply wait for the condition?
Branch Prediction: Guessing Well Enough to Matter
▶ lab

The CPU has to supply a fetch address before it knows the branch outcome, so it predicts one from history. Modern predictors are accurate enough that well-behaved code pays essentially nothing for its branches — which is exactly why the badly-behaved cases stand out so sharply.

Q · How does a CPU guess which way a branch will go, and what makes some branches predictable and others not?
Misprediction: What a Wrong Guess Costs

When the predictor is wrong, everything fetched and executed down the wrong path is discarded and the pipeline refills from the correct address. The cost is not the discarded work — it is the emptiness afterwards, and it scales with how deep the pipeline is.

Q · What exactly happens when a branch prediction turns out to be wrong, and why does it cost what it costs?
Speculative Execution: Doing Work Before You Know You Need It

Modern CPUs execute instructions they may have to discard, on the bet that the guess was right. It works because the guess usually is, and because discarded results never become architecturally visible. What they do leave behind — microarchitectural traces — turned out to matter enormously.

Q · Why does a CPU execute instructions it might have to throw away, and what does that work leave behind when it is discarded?
Branchless Code: A Trade, Not an Upgrade

Replacing an unpredictable branch with arithmetic converts a variable cost into a fixed one. That is a win when the branch mispredicts often and the work it guards is trivial — and a loss in every other case, which is most of them.

Q · When is it actually worth replacing a branch with arithmetic, and when does doing so make things worse?
Out-of-Order & Parallelism →

Modern CPUs are throughput machines: many instructions in flight, executed as their inputs become ready, retired in program order. Renaming, superscalar issue, ILP and what IPC actually tells you.

Out-of-Order Execution
▶ lab

You wrote A, B, C. If B is waiting on a cache miss, the machine will run C first — and then hand you a result indistinguishable from having run them in order. This is the lesson where "the CPU executes my code line by line" stops being a useful model.

Q · If the CPU is free to execute my instructions in a different order than I wrote them, why does my program still produce the answer I expect?
Dependency Graphs: The Real Shape of Your Code

Program order is a line. What the machine actually obeys is a graph — and the longest path through that graph, not the number of nodes in it, is what sets the floor on how fast a loop can run.

Q · Two loops execute the same number of instructions and one is three times slower — what is the machine actually constrained by?
Instruction-Level Parallelism

A single thread, on a single core, with no threading library anywhere in sight, routinely has a dozen operations in flight at once. That is ILP — parallelism the hardware extracts from your sequential code without being asked, and the first thing to understand before reaching for threads.

Q · How much parallelism is my single-threaded code already getting for free, and what stops it from getting more?
Superscalar Execution

A pipelined core finishes one instruction per cycle at best. A superscalar core has several execution units and finishes several — provided your instructions need different units and do not depend on each other. Port contention is why the theoretical peak is theoretical.

Q · What actually determines how many instructions my core can complete in one cycle?
Register Renaming

The ISA gives you a handful of register names. Reusing one creates a dependency that has nothing to do with your data — a naming collision, not a real ordering requirement. Renaming maps those names onto a much larger physical file and the false dependency disappears.

Q · If the ISA only defines a small number of registers, how does the CPU keep hundreds of operations in flight without them constantly colliding?
The Reorder Buffer and Precise State

Execution finishes in whatever order the data allows. Something has to put the results back in order before anyone can see them — and that same something is what lets a page fault, an interrupt or a mispredicted branch unwind cleanly instead of corrupting your program.

Q · If operations complete out of order, what makes an exception land at exactly the right instruction?
IPC: Instructions Per Cycle

The ratio that connects "how much work" to "how long it took". It is the most useful single number for diagnosing a CPU-bound loop — and one of the easiest to misuse, because a change that raises IPC can leave the program slower.

Q · What does instructions-per-cycle actually tell me, and when does improving it make the program slower?
SIMD & Vectorization →

One instruction, many elements. What makes a loop vectorizable, what stops the compiler from doing it, and where data-level parallelism sits among the other kinds.

Four Kinds of Parallelism

Instruction-level, data-level, thread-level and core-level parallelism are four different mechanisms with four different requirements, four different costs and four different failure modes. Most confused performance arguments come from conflating two of them.

Q · When someone says "make it parallel", which of the several available mechanisms do they actually mean — and which one does my problem admit?
SIMD: One Instruction, Many Elements
▶ lab

A vector register holds several values and a vector instruction applies one operation to all of them at once. It is the cheapest parallelism on the machine — single-threaded, race-free, and frequently left unused because a single unprovable pointer relationship disabled it.

Q · How does one instruction operate on eight numbers at once, and what does my data have to look like for that to be possible?
Vectorization: Turning a Loop Into Vector Work

The transformation from one-element-per-iteration to many, and the four conditions that have to hold for it to be legal. Most loops that fail to vectorise fail on a single unprovable assumption rather than on anything fundamental.

Q · What exactly has to be true about my loop before it can legally be turned into vector operations?
Auto-Vectorization: Verify, Do Not Assume

Compilers vectorise loops automatically, sometimes. It is a best-effort optimisation with no guarantee, it fails silently, and it can stop working after an unrelated edit — so the only responsible position is to check rather than believe.

Q · How do I find out whether the compiler actually vectorised my hot loop, rather than assuming it did?
Caches & Memory Hierarchy →

The deepest module, because this is where most real programs spend their time. Lines, locality, associativity, replacement, thrashing and prefetching — the machinery behind "why is this loop slow".

The Memory Hierarchy
▶ lab

One big fast memory is not buildable at a price anyone would pay, so machines are built as a stack of progressively larger, slower, cheaper memories that pretend to be one. The gaps between the levels are enormous, and nothing in your source code tells you which level you just hit.

Q · Why is memory built as a hierarchy at all, and how big are the gaps between the levels really?
What a Cache Actually Is

A cache is not a faster memory. It is a small tagged store holding copies of recently used lines, managed entirely by hardware, betting that your program will ask for the same or nearby data again. When the bet pays it is invisible; when it fails it is also invisible, which is the problem.

Q · What is a cache actually doing, and why does it work at all rather than just adding a layer of guessing?
Memory Moves in Lines, Not Variables
▶ lab

The cache has no concept of your variables. It moves fixed-size blocks — typically 64 bytes today — so reading one byte fetches the 63 around it. Almost every practical memory optimisation, and one notorious concurrency bug, follows directly from that one fact.

Q · What is the actual unit of transfer between memory and cache, and what follows from it being larger than my variable?
Spatial Locality
▶ lab

If you touch an address, you will probably touch its neighbours soon. Hardware bets on this at every level — line size, prefetchers, DRAM row buffers — so code that walks memory in order gets most of its data effectively for free, and code that scatters pays full price for every element.

Q · Why does the *order* in which I visit the same data change how long it takes?
Temporal Locality

If you touched something recently, you will probably touch it again. That assumption is what makes keeping copies worthwhile at all — and it is why the size of the data you revisit, rather than the size of the data you own, determines whether a program is fast.

Q · Why does it matter how *recently* I used a piece of data, and what determines whether it is still there when I come back?
Hits, Misses and What a Miss Actually Costs
▶ lab

A miss is not an error; it is a cost, and it is the normal way data arrives. What matters is where the miss is satisfied — one level out, three levels out, or in DRAM — because those outcomes differ by more than an order of magnitude and imply completely different fixes.

Q · What actually happens on a cache miss, and why does a miss rate on its own tell me so little?
Three Kinds of Miss, Three Different Fixes
▶ lab

Compulsory, capacity and conflict misses look identical in a counter and have almost nothing in common as problems. Prefetching helps one, blocking helps another, and layout changes help the third — so classifying the miss is what turns a measurement into a plan.

Q · My miss rate is high — but which kind of miss is it, and does that change what I should do?
Direct-Mapped Caches: One Address, One Home

The simplest way to build a cache: every memory address has exactly one line it is allowed to occupy. Lookup becomes trivial and the hardware stays cheap — but two hot addresses that happen to share an index evict each other forever, while the rest of the cache sits empty.

Q · A cache holds a tiny fraction of memory, so how does the hardware decide where a given address is allowed to live — and what goes wrong when two hot addresses want the same place?
Set-Associative Caches: The Compromise That Won

Give each address a set of N possible homes instead of one. Conflicts stop being catastrophic, lookup stays affordable, and you inherit a new problem — with N candidates, something has to decide which one to evict.

Q · If one legal location per address causes conflicts and unrestricted placement is too expensive to search, what does the hardware actually build instead?
Tag, Index and Offset: How an Address Finds Its Line
▶ lab

A cache does not search. It slices the address into three fields — offset, index, tag — and each field's width is forced by the geometry rather than chosen. Once you can do the split, most cache behaviour stops being mysterious.

Q · Given an address and a cache geometry, how does the hardware work out in constant time whether that address is present — and where?
Cache Replacement: LRU Is the Idea, Not the Implementation

With N ways in a set, a miss requires choosing a victim. Textbooks say least-recently-used. Real hardware implements approximations that are cheaper, sometimes adaptive, generally undocumented, and different between levels on the same die.

Q · When a set is full and a new line arrives, which of the existing lines gets thrown out — and can software rely on the answer?
Cache Thrashing: Load, Evict, Reload, Repeat

Two ways to make a cache useless: overflow it, or arrange for everything you touch to land in one set. Both produce the same signature — a performance cliff at a specific input size or stride, where the curve falls off rather than bending.

Q · Why does performance sometimes collapse abruptly at one particular array size or stride, rather than degrading smoothly as the data grows?
Working Set: Why Performance Falls Off a Cliff

The working set is the data a program actually touches in a window of time. Whichever level of the hierarchy it fits in determines what the program costs — and because the levels are discrete, crossing a boundary produces a step change rather than a gradual decline.

Q · Why does the cost per element stay flat as data grows and then jump abruptly, instead of rising smoothly with size?
Prefetching: The Hardware Guesses What You Will Read Next
▶ lab

A cache miss costs far more than an instruction, so the hardware tries not to take one: it watches your access stream, predicts the next addresses and fetches them early. Predictable patterns get their data before they ask. Pointer chasing does not — which is most of the answer to why an array beats a linked list at the same complexity.

Q · If a miss to main memory costs the equivalent of many arithmetic operations, how does sequential code manage to run fast at all?
Main Memory & DRAM →

Past the last-level cache: how DRAM is organized, why latency and bandwidth are different resources, and how to tell a bandwidth-bound workload from a latency-bound one.

Past the Last-Level Cache
▶ lab

When every cache misses, the request leaves the CPU entirely. It goes to a memory controller that queues it, reorders it against other pending requests, and drives a DRAM device that is nothing like the flat byte array your program believes in.

Q · What actually happens after a load misses every level of cache?
How DRAM Is Organised

DRAM is not a flat array. It is a grid of rows and columns across banks, and reading it means activating a whole row into a buffer first. Whether your next access hits that open row or forces another activation is a several-fold cost difference nothing in your code mentions.

Q · Why does the cost of a DRAM access depend on which address you touched last?
Latency and Bandwidth Are Different Resources
▶ lab

A workload can saturate memory bandwidth while barely being affected by latency, or be crippled by latency while using a fraction of available bandwidth. Conflating the two sends people to the wrong fix — and "the memory is slow" is almost never a complete diagnosis.

Q · Is this workload limited by how long one memory access takes, or by how many bytes per second the memory system can deliver?
When the Memory Bus Is the Bottleneck

Streaming code that touches each byte once cannot be helped by caches, cannot be helped by more cores, and cannot be helped by faster arithmetic. It is limited by how fast bytes arrive, and the only real lever is moving fewer of them.

Q · Why does my loop stop getting faster when I add cores, even though the CPUs are not busy?
When You Cannot Ask the Next Question Yet
▶ lab

Some loops use almost no memory bandwidth and are still dominated by memory. Each access must complete before the next address is even known, so the hardware's ability to overlap misses is worth nothing, and the loop runs at one DRAM round trip per step.

Q · Why is my loop memory-bound when memory bandwidth usage is almost zero?
Data Layout in Memory →

Where the bytes actually sit: alignment and padding, endianness, address arithmetic, why an array beats a linked list at equal complexity, and array-of-structs versus struct-of-arrays.

Alignment: Why Addresses Are Not Arbitrary
▶ lab

Hardware prefers a four-byte value at an address divisible by four. Break that and the penalty ranges from literally nothing, through a silent extra memory access, to a fault that kills the process — and which one you get depends entirely on the architecture.

Q · Why does the address a value sits at matter, when memory is supposedly just bytes?
Padding: Why Your Struct Is Bigger Than Its Fields
▶ lab

A struct with a char and an int is not five bytes. The compiler inserts padding to keep every field naturally aligned, and in a large array of those structs the padding is memory you pay to move but never read.

Q · Why does `sizeof` a struct exceed the sum of its fields, and when is that worth caring about?
Endianness: Which Byte Comes First

The value `0x12345678` is unambiguous. The four bytes it occupies in memory are not — their order depends on the machine. It matters exactly when bytes leave the machine, which is why it is a networking and file-format problem more than a CPU one.

Q · In what order does a multi-byte value actually sit in memory, and when does that order become my problem?
What `arr[i]` Actually Compiles To

Indexing an array is not a lookup. It is arithmetic: base plus index times element size, computed in a single addressing mode on most architectures. That is why arrays are the cheapest random-access structure hardware supports.

Q · What does the machine actually do to turn `arr[i]` into a value?
Both Are O(n). One Is Far Slower.
▶ lab

Traversing an array and traversing a linked list are both linear. On real hardware the array can be an order of magnitude faster, because complexity counts operations and hardware charges for data movement and dependencies.

Q · If traversal is O(n) either way, why is the array so much faster in practice?
Pointer Chasing: The Address You Do Not Have Yet
▶ lab

Any traversal where the next address comes out of the current load runs at one memory round trip per step. It is the mechanism behind slow lists, slow trees, slow graphs and hash lookups that underperform their O(1) label.

Q · Why can the CPU not overlap the memory accesses in a tree or graph traversal?
Array of Structs, or Struct of Arrays?
▶ lab

The same particles can be one array of records or several parallel arrays of fields. Which is faster depends entirely on whether your loop reads most fields of a few records, or one field of many — and the difference is how much of each cache line you actually use.

Q · Should related fields live together in a record, or should each field get its own array?
Data-Oriented Design, Without the Dogma

Organise data around how it is processed rather than around how the domain is modelled. It is a real technique with real wins in hot loops — and a genuinely bad default for code where clarity matters more than cache lines.

Q · When is it worth organising code around the data's memory layout instead of around the problem domain?
Virtual Memory Hardware →

The hardware half of a mechanism the OS owns: the MMU that translates, the page tables it walks, the TLB that caches translations, and the protection bits that make isolation possible.

Every Address Your Program Uses Is Fake
▶ lab

A pointer is not a memory location. It is an index the CPU must translate before anything can be read, and that translation happens on every load and every store, in hardware, before the access can even begin.

Q · Every load and store in my program uses an address that does not physically exist — so who turns it into a real one, and when?
The MMU: Translation and Protection in One Check

The memory management unit is not a lookup table off to the side. It is a gate every load and store passes through, and it answers two questions at once: where does this address really point, and is this process allowed to touch it that way?

Q · What does the MMU actually check on every single memory access, and why can it not be skipped when the answer is obvious?
The Page-Table Walk: Dependent Loads All the Way Down

A translation the TLB does not have must be looked up in tables that live in memory. The lookup is multi-level, each level depends on the one before it, and any of them can miss in cache — which is why a TLB miss is expensive out of all proportion to the work it represents.

Q · If translation needs a table that lives in memory, why is not every memory access at least two memory accesses?
The TLB: A Cache for Addresses, Not Data
▶ lab

The translation lookaside buffer holds recently used virtual-to-physical mappings so the common case skips the walk entirely. It is small, it is split by purpose, and its capacity is measured in pages — which makes its working set a completely different quantity from your data cache's.

Q · How does the CPU avoid walking the page table on every access, and what decides whether that shortcut works?
When Translation Itself Is the Bottleneck

A profile shows memory stalls. The data fits in cache. Cache miss rates look fine. The stalls are real and the usual suspects are all innocent — because the CPU is not waiting for data, it is waiting to find out where the data is.

Q · My data fits in cache and the profile still shows memory stalls — can address translation itself be the bottleneck?
Huge Pages: More Coverage per Entry, and What It Costs

If the TLB can only hold so many entries, make each entry cover more memory. That is the whole idea, and it can transform a translation-bound workload — but it costs memory, complicates allocation, and the transparent variety can make things worse.

Q · If the TLB can only hold so many entries, can I make each one cover more memory — and what do I give up?
What Actually Stops One Process Reading Another's Memory

Process isolation is not a promise the operating system makes and enforces in software. It is a consequence of permission bits the MMU checks on every access, and of the simple fact that one process has no way to name another's physical memory at all.

Q · What actually stops one process from reading another's memory — and what does that protection not cover?
Why Kernel Mode Is Actually Privileged

User mode and kernel mode are not a convention the kernel politely observes. They are a hardware state, and the CPU refuses certain instructions and certain memory outright depending on which one it is in.

Q · What makes kernel mode actually privileged, rather than just a convention the kernel follows?
Why a System Call Costs More Than a Function Call

A function call is a jump and a stack push. A system call changes the privilege level, redirects control to an address you do not choose, and disturbs enough microarchitectural state that the cost outlives the call itself.

Q · Why does calling into the kernel cost so much more than calling a function, when both are just a jump somewhere else?
Multicore & Coherence →

What changes when there is more than one core: hardware threads versus OS threads, the coherence protocol keeping caches consistent, false sharing, NUMA and why moving a thread costs cache.

What a Second Core Actually Adds

Eight cores is not one core that goes eight times faster. It is eight execution engines, each with private caches, sharing one last-level cache and one memory system through an interconnect — and that shared half is where multicore performance is usually won or lost.

Q · What is actually duplicated when a chip gains a second core, and what is still shared?
Core, Hardware Thread, Software Thread

Three things routinely called "a thread", stacked on top of each other. A core is silicon that executes. A hardware thread is an execution context inside it. A software thread is an OS bookkeeping structure that gets mapped onto one. Conflating them is how "we have 16 threads" becomes a wrong capacity estimate.

Q · When someone says "this machine has 16 threads", what exactly are they counting?
SMT: Two Contexts, One Core

Simultaneous multithreading gives one physical core a second register set so it can switch instruction streams instantly and fill cycles the first stream would waste. It does not add execution units, and it does not add a core. On the right workload it is a solid gain; on the wrong one it is negative.

Q · What does a second hardware thread on the same core actually give me, and when does it give me nothing?
Hardware Threads Are Not OS Threads

A §224 distinction the whole concurrency stack rests on. A hardware thread is a fixed execution context built into silicon. An OS thread is an allocated software object. The OS multiplexes many of the second onto few of the first, and every scheduling cost you can measure lives in that mapping.

Q · Where does a software thread stop being a data structure and start being something a core executes?
Cache Coherence: Why Shared Memory Works At All
▶ lab

Two cores cache the same variable. One writes. Nothing in your code tells the other core to look again — yet it must not read stale data. Coherence is the hardware protocol that guarantees it, running underneath every shared-memory program, and it is emphatically not free.

Q · When one core writes a variable that another core has cached, what makes the second core see the new value?
MESI and Its Relatives
▶ lab

Coherence needs each cached line tagged with what the core is allowed to do with it. MESI — Modified, Exclusive, Shared, Invalid — is the canonical four-state answer and the one worth learning. It is a family, not a standard: real chips extend it, and which variant yours uses is usually undocumented.

Q · What state does a core track per cache line, and what transitions does a read or a write trigger?
False Sharing: Independent Data, Shared Line
▶ lab

Two threads update two different variables. They never touch each other's data and the code is obviously correct. Throughput is worse than single-threaded, because the two variables happen to sit in one cache line and the hardware shares by line, not by variable.

Q · Why do two threads writing to genuinely separate variables slow each other down?
NUMA: Not All Memory Is Equally Far
▶ lab

On a multi-socket machine, memory is attached to sockets. A core reaching its own socket's memory is on a short path; reaching the other socket's memory crosses an inter-socket link. Same instruction, same address space, materially different cost — and the allocator decides which you get.

Q · Why does the same memory access cost more depending on which core is doing it?
Thread Affinity: Pinning and Its Price

Affinity constrains which cores a thread may run on. It buys cache locality, NUMA locality and predictable latency, and it costs the scheduler's ability to balance load. It is a genuine tool for latency-critical work and a genuine way to make a machine slower if applied by reflex.

Q · When does restricting a thread to specific cores make things faster, and when does it just tie the scheduler's hands?
Cache Warmth and the Real Cost of Migration

The expensive part of a context switch is not saving registers. It is that the thread resumes on a core whose caches and TLB hold someone else's data, so it must take a burst of cold misses to rebuild a working set that existed perfectly well a moment ago somewhere else.

Q · Why does a context switch cost far more than saving and restoring registers?
Memory Ordering & Atomics →

Why the order you wrote is not the order the machine performs, what a barrier actually constrains, and the hardware primitives — compare-and-swap and friends — every lock is built from.

Sequential Consistency: The Model You Already Have

Everyone reasons about shared memory as if there were one global order of operations that every core agrees on. That model is intuitive, teachable, and not what any mainstream CPU implements — which is exactly why it is worth stating precisely before taking it away.

Q · What is the mental model of shared memory that almost everyone starts with, and in what precise way is it wrong?
Why Your Loads and Stores Happen Out of Order

Two independent agents reorder your memory operations before any other core sees them: the compiler, which rewrites the code, and the CPU, which executes and retires it out of order. Neither is malfunctioning, and on one core neither is detectable.

Q · Who reorders my memory operations, which reorderings are actually permitted, and why can I never see it happening on a single thread?
Store Buffers: Where Your Writes Wait

A store instruction finishes long before the value reaches coherent cache. In between it sits in a per-core queue that only its own core can see — which is the concrete mechanism behind the one reordering even x86-64 permits.

Q · Where does a value actually go when a store instruction retires, and why can another core still read the old value afterwards?
Memory Barriers: Ordering, Not Flushing

A barrier is one of the most misdescribed instructions in computing. It does not flush caches, it does not push data anywhere, and it does not lock anything. It constrains the order in which one core's memory operations may become visible relative to each other.

Q · What does a memory barrier actually constrain, and why is "it flushes the write buffer to memory" the wrong mental model?
Hardware Memory Models Are Not Language Memory Models

Your CPU has a memory model. Your language has a different one. The compiler stands between them, and code that "works on x86 and breaks on ARM" has almost always been written against the hardware model of the machine it was tested on.

Q · What is the difference between the memory model my CPU implements and the one my language specifies, and why does code that works on x86-64 break on AArch64?
Atomic Instructions: What the Hardware Actually Guarantees

An atomic read-modify-write is a single instruction that no other core can observe half-finished. That is a narrow and precise guarantee, and it is routinely mistaken for a much broader one about program correctness.

Q · What does the hardware actually promise when an operation is atomic, and what does it conspicuously not promise?
Compare-and-Swap: The Primitive Everything Is Built On
▶ lab

Change this value, but only if it is still what I last saw. That conditional write is what makes lock-free algorithms possible, and the trap in it — that "still the same value" is not the same as "nothing happened" — is called ABA.

Q · How does compare-and-swap turn a racy read-modify-write into a safe one, and what is the failure it cannot detect?
What a Mutex Actually Does

A mutex is not an operating-system object you call into. In the uncontended case it is one atomic instruction and no system call at all — which is why an uncontended lock is nearly free and a contended one costs thousands of times more.

Q · What happens in the machine when I lock a mutex, and why is the cost so wildly different depending on whether anyone else holds it?
Interrupts, DMA & I/O →

How the world outside the CPU gets in: interrupts against polling, DMA moving bytes without the CPU copying them, the path to storage, and why a CPU cache is not the OS page cache.

Interrupts: How Hardware Gets the CPU's Attention

A network card cannot call a function. It raises a line, and the CPU abandons what it was doing at the next instruction boundary. The handler's instruction count is the smallest part of what that costs.

Q · A device runs on its own clock and has no way to call your code — so how does it get the CPU to notice that something happened?
Polling versus Interrupts

Being told costs a fixed amount per event; asking costs a fixed amount per unit of time. Which is cheaper is arithmetic, and above a crossover rate the "wasteful" busy loop wins decisively.

Q · Should the CPU wait to be told an event happened, or keep asking — and what actually decides which one is cheaper?
DMA: Moving Bytes Without the CPU

A disk read does not consume a core, because the CPU never touches the bytes. It writes a descriptor, the device masters the bus and writes straight into RAM, and the CPU finds out afterwards.

Q · When a device delivers a megabyte of data, who actually moves the bytes into memory — and what does the CPU do while that happens?
I/O Architecture: The Interconnect Is a Shared Resource

Storage, network and accelerators do not each get a private path to memory. They share an interconnect with finite bandwidth, and a saturated link explains slowdowns that look like they belong to whichever device you happened to be watching.

Q · How does the CPU actually reach a disk, a network card and a GPU at the same time — and what happens when all three are busy?
Memory-Mapped I/O: When a Store Is Not a Store

Device registers live in the address space, so talking to hardware looks exactly like writing to memory. It is not memory: the write has a side effect, the read may change state, and every optimisation the machine normally applies has to be turned off.

Q · If a device register appears at an address, why can I not just read and write it like any other variable?
PCIe: Lanes, Generations and the Transfer Budget

The link between a CPU and an accelerator is not free capacity. It has a width, a generation and a ceiling — and for many workloads the transfer over it, not the computation at either end, is what sets the runtime.

Q · How much data can actually move between the CPU and a GPU or SSD, and when does that link become the thing that limits the program?
The Storage Path: Why One Small Read Is the Worst Case
▶ lab

An SSD is a parallel device pretending to be a disk. Give it one request at a time and you measure its latency; give it many and you measure its throughput — and those two numbers are not related the way rotational intuition expects.

Q · A read reaches the SSD in microseconds of CPU work and takes far longer to return — where does that time go, and why does issuing more requests not make it worse?
CPU Cache Is Not the Page Cache

Both are called cache, both make things faster, both live in the machine. One is hardware holding lines of physical memory and you cannot address it; the other is ordinary RAM the kernel fills with file data and you can control it precisely.

Q · When someone says "it is in cache", which cache do they mean — and does the answer change what I should do about it?
GPUs & Accelerators →

Throughput hardware and its price: why GPUs win on wide regular work, why transfers and divergence undo it, and what specialized accelerators trade away to be fast at one thing.

CPU or GPU: Two Bets About What Work Looks Like
▶ lab

A CPU spends its transistor budget making one instruction stream go fast — speculation, out-of-order issue, large caches. A GPU spends a comparable budget on many simple lanes running the same operation over different data. Neither is faster; the shape of your work decides which bet pays.

Q · What is a CPU doing with all the transistors a GPU spends on lanes, and how do I tell which of the two my workload actually wants?
What Is Actually Inside a GPU
▶ lab

A host CPU, a device with many compute units, an unusually large register file, a small block of programmer-managed on-chip memory per unit, and a large pool of high-bandwidth global memory. The register file being large — and being the thing that limits how many lanes stay resident — is the part that surprises people.

Q · What are the parts of a GPU, and which of them ends up limiting how much parallelism I actually get?
Lanes, Divergence and Coalescing

Lanes execute in lockstep groups: one instruction, many lanes, different data. Two consequences follow and both are unlike anything on a CPU — a data-dependent branch makes the group execute both sides in sequence, and the memory addresses the lanes request must line up or the traffic multiplies.

Q · Why does an `if` inside a GPU kernel cost so much more than the same `if` on a CPU, and why does the access pattern matter more than the number of bytes?
The Transfer You Forgot to Count
▶ lab

Host memory and device memory are separate pools connected by a bus that is slow relative to both. A kernel ten times faster than the CPU loses if you pay two crossings to use it — so the real question is not how fast the kernel is, but at what input size the whole path overtakes staying put.

Q · When does the cost of getting data onto the device and back exceed the time the device saves?
What GPU-Friendly Work Has in Common

Dense linear algebra, graphics, model training and inference, image processing, scientific simulation. The list looks unrelated until you notice that every entry is wide, regular and reuses each loaded byte many times — and that the workloads which disappoint share the opposite properties.

Q · What do the workloads that genuinely suit a GPU have in common, and how do I tell in advance whether mine is one of them?
Accelerators: The Specialization Spectrum

GPUs, AI accelerators, NPUs and FPGAs are not four unrelated products. They are points on one axis running from fully general to fully fixed, and each step along it trades away the ability to run arbitrary work in exchange for doing one kind of work with less silicon and less power.

Q · What separates a GPU from an AI accelerator from an FPGA, and what does each give up to be good at what it is good at?
The Specialization Trade-off

Specialized hardware is more efficient because it does less. That is a genuine gain and a genuine risk: the efficiency comes from decisions frozen at design time, and workloads have a habit of changing shape faster than silicon can be replaced.

Q · What exactly am I giving up when I choose more specialized hardware, and how do I decide whether the exchange is worth it?
LLM Inference Is a Memory Bandwidth Problem
▶ lab

The path from prompt to token runs through matrix operations on an accelerator, and the surprise is which resource binds. Generating tokens one at a time reads the entire model from memory per token, so inference is usually bandwidth-bound rather than compute-bound — which is why model size and memory bandwidth dominate the conversation.

Q · When an agent sends a prompt to a model, what does the hardware actually do — and which resource runs out first?
Model Memory, and Why the Naive Number Is Always Too Low

Parameters times bytes per parameter gives a floor, not an answer. Activations, the KV cache that grows with context and concurrency, and runtime overhead all sit on top — which is why a model that "fits in memory" by the simple calculation frequently does not.

Q · How much memory does serving a model actually need, and what does reducing precision buy in hardware terms?
Hardware Performance Analysis →

Reading the machine: performance counters, CPI, telling compute-bound from memory-bound, and every way a microbenchmark will lie to you about frequency, caches and dead code.

The CPU Counts Itself
▶ lab

Every modern CPU carries a small unit whose only job is to tally what the rest of the chip did: cycles, instructions retired, misses at each cache level, mispredicted branches, stalled cycles. It is the only direct evidence you will ever get about the hardware — and it is sampled, approximate, and named differently on every chip.

Q · How does the CPU report what it was actually doing, and how much of that report can I believe?
CPI and IPC: The Number Everyone Misreads

Cycles per instruction, and its reciprocal instructions per cycle, describe how smoothly work is flowing through the machine. Neither is a measure of performance. A change that halves CPI while tripling instruction count made the program slower, and a vectorized loop that runs twice as fast often shows a worse CPI than the scalar loop it replaced.

Q · What does cycles-per-instruction actually tell me about my program, and why is a better CPI sometimes a worse program?
Busy Is Not the Same as Working

A core showing 100% utilisation may be executing a dense stream of arithmetic, or it may be stalled almost the entire time waiting for data that has not arrived. The operating system reports both as "busy". They are different problems with disjoint fixes, and only the counters can tell them apart.

Q · The core is pinned at 100%. Is it doing work, or is it waiting — and how would I know the difference?
Misses That Overlap Are Nearly Free

A cache miss costs a great deal if the core has nothing else to do, and almost nothing if it does. Modern cores keep several misses outstanding at once, so ten independent misses can cost barely more than one — while ten dependent misses cost ten times as much. This is why miss counts alone never predict runtime.

Q · Why do two loops with the same number of cache misses take completely different amounts of time?
Your Code Is Data Too

Instructions are fetched from memory through their own cache, and that cache is small. A hot loop that fits runs at full speed; a sprawling call graph with aggressive inlining can spend a large fraction of its cycles waiting for instructions to arrive — a stall that data-focused profiling is structurally unable to see.

Q · Why does a program with excellent data locality still stall, and why can inlining make it slower?
Every Way a CPU Microbenchmark Lies

A microbenchmark measures what it measures, which is frequently not what you meant. The compiler deletes work whose result is unused, the caches and predictors are warm in ways production never is, the clock speed moves underneath you, and the timer itself costs more than the operation. Each of these has produced published results that were simply wrong.

Q · Why does my microbenchmark say this code is fast when production says otherwise?
The Clock Is a Variable

The number printed on the box is a nominal figure, not an operating one. Real clock frequency moves continuously with load, thermal headroom, power budget, how many cores are active, and even which instructions are executing — wide vector code frequently runs at a lower clock than scalar code on the same chip.

Q · Why do identical runs of the same code take different amounts of time on the same machine?
The First Ten Seconds Lie

Silicon has a temperature limit, and the only lever the chip has to stay under it is to slow down. A workload that starts on a cool chip runs at one speed and settles at a lower one, which is why burst performance and sustained performance are different numbers and why short benchmarks systematically flatter the machine.

Q · Why does a long-running workload get slower over time even though nothing about the code changed?
Performance Per Watt

Energy, not time, is the constraint that actually binds on phones, on laptops and across datacentres. The configuration that finishes soonest is frequently not the one that uses least energy, and the relationship between the two is non-linear enough that "run slower to save power" is often exactly wrong.

Q · When is the fastest configuration not the right one, and how does energy behave differently from time?
Throughput Improved, Latency Did Not

Nearly every technique modern CPUs use — pipelining, superscalar issue, out-of-order execution, speculation — increases the number of operations completed per unit time without reducing, and sometimes while increasing, the time any single operation takes. This is why decades of architectural progress leave a dependent chain almost exactly as slow as it was.

Q · Why has all this architectural progress made parallel work so much faster and dependent work barely faster at all?
Where This Shows Up →

Cache-aware algorithms and tiling, what the compiler did before the CPU saw your code, side channels as a consequence of speculation, and the hardware under virtual machines and containers.

Cache-Aware Algorithms
▶ lab

Two algorithms with identical asymptotic complexity can differ by an order of magnitude in wall clock, because complexity analysis counts operations and hardware charges for data movement. Blocking, compact layouts and node sizes matched to the transfer granularity are all the same idea: arrange the work so that data pays its travel cost once.

Q · Why can two algorithms with the same big-O differ by an order of magnitude in wall clock, and how do you design for the memory hierarchy rather than for the instruction count?
Matrix Tiling: Same Arithmetic, Ten Times Faster
▶ lab

The tiled matrix multiply performs exactly the same multiply-accumulate operations as the naive triple loop, in a different order. It wins because a block of each matrix is brought into cache once and used many times, instead of a row or column being re-fetched on every pass.

Q · Why does reordering the loops of a matrix multiply — without changing a single arithmetic operation — make it dramatically faster on large matrices?
The Compiler Reordered It Before the CPU Did

Between the line you wrote and the work the machine performs sit two independent reordering layers: a compiler that transforms code under the language's rules, and a processor that executes the result out of order under the architecture's rules. Each preserves its own notion of observable behaviour, and neither preserves the order you wrote.

Q · What actually happens to my source code between writing it and the CPU executing it, and who is allowed to reorder what?
Why Reading the Source Cannot Tell You the Cost

Two adjacent lines of source imply neither two instructions nor two steps in time. Source code specifies *what result is required*, and it is an excellent tool for reasoning about correctness — but it deliberately says nothing about instruction count, ordering or cost, which is exactly why measurement exists.

Q · If I cannot trust source order to tell me what the machine does, what is source-level reasoning actually good for?
Side Channels: When Performance Optimisations Leak

Every mechanism that makes a CPU fast by remembering something — caches, branch predictors, translation buffers — creates state that outlives the operation and can be observed indirectly through timing. Information leaks not through what a program outputs, but through how long other things take afterwards.

Q · How can a program leak secrets without ever outputting them, purely through the timing effects of hardware optimisations?
Spectre and Meltdown: When Speculation Crossed a Boundary

In 2018 a class of vulnerabilities showed that speculative execution — a two-decade-old performance technique — could be steered into performing accesses that architecturally never happened, while leaving microarchitectural traces that a timing side channel could read. The durable lesson is not the specific bug but its shape: a performance optimisation created a security boundary violation, and the mitigations cost real performance.

Q · How did speculative execution — a pure performance feature — turn into a security vulnerability, and what did fixing it cost?
The Hardware That Makes Virtual Machines Possible

Running a guest operating system that believes it owns the machine used to require interpreting or rewriting its privileged instructions. Hardware virtualization support added a mode below the kernel's, so a guest can run its own privileged code at native speed while the hypervisor stays in control — and a second layer of address translation so guest memory works without the hypervisor intervening on every access.

Q · What does a CPU actually provide that lets a guest operating system run privileged code at full speed without escaping its virtual machine?
What a vCPU Actually Is

A vCPU is not a core. It is a schedulable thread of execution that the hypervisor multiplexes onto physical hardware, sharing that hardware with other guests. This is why cloud instance performance varies, why steal time exists, and why the count in the instance description does not translate into a guaranteed amount of compute.

Q · When a cloud instance advertises eight vCPUs, what have I actually been given — and why does identical code sometimes run at different speeds on it?
From malloc to Cache Lines
▶ lab

An allocation call returns a pointer, but between that call and a cache line being filled sit an allocator, a virtual address space, a page fault, a physical frame chosen by the kernel and finally the hardware that transfers the line. Each layer shapes where your data lands, which is why allocation pattern becomes cache behaviour.

Q · What happens between calling an allocator and the data actually occupying a cache line, and why does allocation pattern determine cache behaviour?