English
A processor is a machine that executes instructions. That sentence is both true and almost entirely uninformative — it is like saying a car is a machine that moves. The interesting questions are how, how fast, and at what cost. A modern processor executes tens of billions of instructions per second, but any individual instruction arrives at the execution unit in perhaps a nanosecond; the gap between those numbers is filled by an elaborate set of techniques — pipelining, out-of-order execution, branch prediction, speculative execution — that allow the processor to have many instructions in flight simultaneously. Understanding what those techniques are, when they help, and when they introduce problems is what computer architecture is about.
The subject has three levels that are worth keeping distinct. The digital abstraction sits at the bottom: the convention that physical electrical phenomena can be treated as discrete binary signals, allowing circuits to be designed using Boolean logic rather than analog electronics. This fiction is engineered to hold within tolerances, and modern processors maintain it reliably enough that the analog reality is invisible to software. The instruction-set architecture (ISA) is the formal contract between hardware and software: the complete specification of what instructions a processor can execute and what they do. Software is written to the ISA; hardware implements it. The ISA is designed for decades-long stability — x86 code from the 1980s still runs correctly on current Intel processors. The microarchitecture is the implementation beneath the ISA: how actual transistors, pipelines, and cache hierarchies realize the ISA contract. Microarchitecture changes with every processor generation, is proprietary and typically undocumented in full, and is the layer where most of architecture’s interesting engineering happens.
This subject sits at the foundation of the Systems Axis. Operating systems (§4.2) rely on hardware mechanisms — privileged modes, memory management units, interrupts, atomic instructions — that the ISA defines. Compilers (§4.7) translate programs into the instruction sequences that microarchitecture will execute efficiently or wastefully depending on how well the compiler understands the target. Every performance measurement in every downstream subject is ultimately a consequence of how hardware executes the code. A practitioner who treats hardware as a black box accumulates invisible costs: missed performance, misdiagnosed bugs, and security vulnerabilities that are invisible without the microarchitectural picture.
Prerequisites: Programming (§2.1) — you need to know what programs do before understanding how hardware executes them. Discrete mathematics (§2.2) — Boolean logic, number representations, and reasoning about state machines. Algorithms (§2.6) — cost analysis becomes concrete when you understand where the costs come from.
From Relay Calculators to Speculative Execution
Mechanical calculators existed for centuries before the electronic computer, but the electronic stored-program computer — in which instructions and data live together in the same memory, and the machine reads and executes them automatically — was a specific invention of the 1940s, and its architecture has organized nearly all computing since.
The conceptual design is usually attributed to John von Neumann’s 1945 draft report describing the EDVAC, though the design was worked out collaboratively with J. Presper Eckert, John Mauchly, and others. The von Neumann architecture separates a central processing unit (CPU) from main memory: the CPU fetches an instruction from memory, decodes it, executes it, and writes the result back, then repeats. This fetch-decode-execute cycle is still the conceptual model for how processors work, though the physical reality of modern processors departs from it in almost every detail. The simplicity of the model — one instruction at a time, in order, with uniform memory — made it easy to reason about programs and implement in early hardware. Those same simplifying assumptions became the obstacles to performance that four decades of microarchitectural innovation has worked to overcome.
The first major performance technique was pipelining, borrowed from industrial assembly lines. If execution can be broken into stages — fetch, decode, execute, writeback — and different instructions can occupy different stages simultaneously, throughput improves even though no individual instruction completes faster. The IBM Stretch (1961) was among the first computers to pipeline instruction execution. The Intel 8086 (1978) used a simple two-stage pipeline. By the 1990s, deep pipelines of ten to twenty stages were common, allowing high clock rates because each stage performed less work. The cost of pipelining is complexity: instructions with data dependencies must stall while waiting for results from earlier instructions, branches disrupt the pipeline because the processor does not know which instruction to fetch next until the branch executes, and exceptions require unwinding partially executed instructions. Managing these hazards — data hazards, control hazards, structural hazards — is a central problem of pipeline design.
The RISC vs. CISC debate of the 1980s defined a generation of architectural thinking. Complex Instruction Set Computers (CISC) — the x86 from Intel, the VAX from DEC, the Motorola 68000 — offered rich instruction sets that reduced the number of instructions a program required by encoding common operations as single complex instructions. The argument for CISC was that fewer instructions meant faster programs, especially when memory was expensive and code density mattered. Reduced Instruction Set Computers (RISC) — MIPS from Patterson and colleagues at Berkeley, SPARC from Sun, the IBM 801 — offered simple instructions that executed in predictable single cycles and pipelined cleanly. David Patterson’s and John Hennessy’s research in the early 1980s showed, empirically, that CISC programs did not actually execute fewer instructions than equivalent RISC programs for typical compiler-generated code, and that simple pipelined RISC designs outperformed the complex CISC designs of the day. The RISC philosophy — load/store architecture, fixed-width instructions, large register files, avoiding implicit state — became the dominant design methodology. ARM, the architecture in nearly every smartphone, is RISC. RISC-V, the open architecture now being adopted across academia and embedded systems, is RISC. Even x86, nominally CISC, is internally decoded into RISC-like micro-operations by its execution engine.
The 1990s brought superscalar execution: issuing multiple instructions per clock cycle by detecting independent instructions and executing them in parallel. This required out-of-order execution — allowing instructions to execute in any order consistent with their data dependencies, regardless of the order they appear in the program. Tomasulo’s algorithm, developed for the IBM 360/91 in 1967 but not widely adopted until the 1990s, provides the standard mechanism: instructions are issued into a queue, operand-tracking hardware detects when each instruction’s inputs are available, and instructions execute as soon as their operands are ready. Combined with register renaming — maintaining multiple physical copies of logical registers to eliminate false dependencies — out-of-order processors can sustain high instruction throughput even when program code has many potential hazards.
Branch prediction became critical when deep pipelines were combined with superscalar execution. A modern processor with a twenty-stage pipeline executes the branch instruction two dozen instructions after it has started executing the following instructions speculatively. If the prediction is wrong, all of those instructions must be discarded and execution restarted from the correct path — a penalty of twenty cycles or more. Branch predictors evolved from simple static heuristics (backward branches are usually taken, forward branches usually not — a heuristic for loops and conditionals respectively) through two-bit counters tracking individual branch history through tournament predictors combining multiple prediction schemes to modern neural predictors using perceptron-like structures. Current Intel and AMD processors achieve prediction accuracy above 95% on most workloads — impressive given that the prediction must be made in the first pipeline stage and the branch resolves much later.
The clock speed era ended around 2005. From the 1970s to the early 2000s, processor clock rates doubled roughly every two years — from megahertz to gigahertz across two decades. This improvement came from manufacturing advances (smaller transistors, lower capacitance) that allowed faster switching. But the physical relationship between clock speed, voltage, and power dissipation placed a ceiling on further clock speed increases: higher clock speeds required higher voltage for reliability, and power dissipation scales as the cube of voltage. By 2005, pushing clock speeds further would have required cooling systems beyond practical engineering. Intel’s NetBurst microarchitecture, which had pushed to 4 GHz, was abandoned in favor of the lower-power, lower-clock Core architecture. The era of free performance improvements from clock scaling was over.
The response was multicore: put multiple processor cores on a single chip, sharing the cache hierarchy and memory controller. A chip with four cores at 2 GHz does roughly the same number of operations per second as one core at 8 GHz, using substantially less power, and without hitting the thermal ceiling. From 2005 onward, most performance improvements in general-purpose processors came from adding cores rather than from higher clock speeds. Consumer processors went from two cores to four, eight, sixteen, and beyond; server processors now commonly have sixty-four or more cores. The challenge that followed — how to write software that exploits this parallelism effectively — is one of computing’s most persistent unsolved problems, and most application software remains only partially able to exploit the parallelism available to it.
The discovery of Spectre and Meltdown in January 2018 was the most significant architectural security event in decades. Both vulnerabilities exploited a gap between the ISA specification and the microarchitectural reality. The ISA specifies that a process can access only its own memory; the microarchitecture, pursuing performance, speculatively executes instructions from forbidden memory regions and uses the results to influence cache state, even though the speculative results are never committed. An attacker can infer the forbidden memory contents by observing timing differences in cache access — a side channel that the ISA’s access control cannot prevent because it operates at the microarchitectural level. Meltdown exploited a specific optimization in Intel processors and was mitigated primarily in software. Spectre was broader, exploiting the fundamental behavior of speculative execution, and has proven much harder to mitigate without significant performance cost. The vulnerabilities revealed that the performance optimizations accumulated over forty years had created security risks that the ISA specification did not address and the microarchitecture had not considered. The architectural community has since devoted significant effort to architectural support for confidential computing — including Arm’s Confidential Compute Architecture and Intel’s TDX — that attempts to provide hardware-enforced security boundaries that are robust to microarchitectural side channels.
The contemporary landscape is defined by specialization. General-purpose processors have plateaued in single-thread performance; further gains require specialized hardware. GPUs, originally designed for graphics rendering, accelerate the dense matrix operations that machine learning requires by executing thousands of operations in parallel across their many cores. Google’s TPUs (Tensor Processing Units) specialize further, optimizing specifically for the neural network computations at the heart of their search and translation infrastructure. Apple’s Neural Engine, in every modern iPhone, provides similar dedicated inference acceleration. The transition from general-purpose to specialized hardware is the dominant trend, and the architectural challenge is providing software abstractions — programming models, compilers, runtime systems — that allow programmers to target heterogeneous hardware without rewriting code for each accelerator.
The Memory Hierarchy, Performance, and the Cost of Computation
Memory: The Dominant Bottleneck
The processor’s relationship to memory is the central performance problem of contemporary computer architecture. A modern high-performance core can execute several instructions per clock cycle, at clock rates of 3-5 GHz; accessing main memory takes approximately 60-100 nanoseconds — 200-500 clock cycles. If every instruction required a main memory access, the processor would sit idle 99% of the time. Processors are fast; memory is slow; the gap is large and has grown for decades.
The solution is a memory hierarchy: a set of successively larger and slower storage levels, each acting as a cache for the level below. Registers hold a handful of values accessible in a fraction of a cycle. L1 cache holds tens of kilobytes accessible in 4-5 cycles. L2 cache holds hundreds of kilobytes accessible in 12-15 cycles. L3 cache holds tens of megabytes accessible in 30-50 cycles. Main memory holds gigabytes accessible in 200-500 cycles. When a needed value is in L1 cache — a cache hit — execution proceeds quickly. When it is not — a cache miss — execution stalls while the cache fetches the value from the next level. The fraction of accesses that hit in each level — the hit rate — determines performance.
The memory hierarchy works because of locality: real programs exhibit both temporal locality (recently accessed data will be accessed again soon) and spatial locality (data near recently accessed data will be accessed soon). Caches exploit temporal locality by keeping recently accessed data in fast storage. Cache lines — typically 64 bytes — exploit spatial locality by fetching not just the requested value but the surrounding bytes together, making subsequent accesses to nearby data free.
Cache misses are the dominant performance problem for most real software. Matrix multiplication, traversal of large arrays, and random-access data structures each have distinct cache behavior. Matrix multiplication, when coded naively, accesses elements in column order for one operand, causing cache misses on almost every access for large matrices; cache-blocked (or tiled) implementations access small subblocks that fit in cache, reducing misses by an order of magnitude. Linked-list traversal follows pointers scattered across memory, generating a cache miss on nearly every node access; array-based alternatives avoid this but require different algorithmic structure. Hash table lookup with a good hash function generates a predictable pattern of memory accesses; with a poor hash function or high load factor, it generates expensive cache misses.
NUMA (Non-Uniform Memory Access) architectures complicate the hierarchy further in multiprocessor systems: each processor has local memory that it can access quickly and remote memory (attached to other processors) that takes longer. Software that is NUMA-unaware may place data in memory that is remote to the processor accessing it, wasting significant bandwidth and adding latency.
Pipeline and Execution: Where Performance Is Made and Lost
Modern processors exploit instruction-level parallelism (ILP) through pipelining, out-of-order execution, and superscalar dispatch. Understanding when ILP can be exploited and when it cannot determines which code patterns are fast.
Data dependencies limit ILP. If instruction B requires the output of instruction A, B cannot issue until A completes. A sequence of dependent additions is limited to one per clock cycle even on a superscalar processor, because each addition depends on the previous one. Independent operations can issue simultaneously: computing a + b and c + d in parallel takes half the time of computing them sequentially on a two-way superscalar processor.
Branch mispredictions waste pipeline throughput. When a misprediction is detected — typically at the end of the pipeline — all instructions issued after the branch must be flushed and execution restarted at the correct path. With a twenty-stage pipeline, a misprediction costs twenty cycles. Code with unpredictable branches — branches whose outcome cannot be determined from local history — runs significantly slower than code with predictable branches. This is why sorting an array before performing a conditional operation on it can be faster than performing the operation on an unsorted array, even though sorting requires additional work: sorted data makes the branch predictable.
Memory-level parallelism (MLP) allows multiple cache misses to be in flight simultaneously. Hardware prefetchers detect sequential or strided access patterns and fetch data before it is requested. Software prefetch instructions allow compilers or programmers to request data explicitly. For workloads that access memory sequentially — scanning a large array — hardware prefetchers work well and hide memory latency effectively. For workloads with irregular access patterns — pointer chasing in linked structures, random access to large hash tables — prefetching is ineffective and each cache miss stalls execution.
Parallelism: Cores, Vectors, and Accelerators
Single-core performance improvements from ILP have largely plateaued; further performance gains come from parallelism at larger scales.
Thread-level parallelism (TLP) uses multiple cores executing independent threads. Shared-memory multiprocessors require coherence: when one core writes to a location, the other cores must see the updated value, not a stale cached copy. Cache coherence protocols — MESI (Modified, Exclusive, Shared, Invalid) is the standard — maintain consistency by tracking the state of each cache line across all processors. False sharing occurs when two cores repeatedly modify different variables that happen to share a cache line; the coherence protocol forces the cache line to bounce between processors at cache-miss cost even though no actual sharing is occurring. False sharing is a common performance bug in concurrent code.
Vector instructions perform the same operation on multiple values simultaneously. SSE, AVX, and AVX-512 on x86; NEON and SVE on ARM; all provide SIMD (Single Instruction Multiple Data) execution. A 256-bit AVX2 instruction operating on 32-bit floats processes eight values per instruction. Well-structured numerical code — the inner loops of matrix multiplication, convolution, dot products — can be vectorized by compilers or by explicit intrinsics, achieving significant speedups on compatible hardware. Unstructured code with complex control flow or gather/scatter memory access patterns vectorizes poorly.
GPUs provide massive parallelism at the cost of restrictive programming models. A modern GPU contains thousands of shader processors organized into streaming multiprocessors (SMs), each executing a warp of 32 threads simultaneously. GPUs achieve high throughput on data-parallel workloads — matrix multiplication, convolution, element-wise operations — where the same computation applies to thousands of data points independently. They are inefficient for irregular control flow, small batch sizes, or work that requires frequent communication between threads. The CUDA programming model (NVIDIA) and its alternatives (ROCm, Metal, SYCL) expose this parallelism to programmers; understanding the threading model and memory hierarchy of the target GPU is essential for achieving high performance.
What Studying This Changes
Computer architecture changes what a practitioner can see in running code.
The first change is the ability to reason about performance from physical first principles. Cache misses, branch mispredictions, and pipeline stalls are not mysterious performance degradations — they are predictable consequences of the microarchitectural structure. A practitioner who understands the memory hierarchy can look at a data structure and predict whether accesses to it will hit in L1, L2, or main memory; can look at a loop and estimate how many cycles each iteration will take; can identify whether a performance problem is memory-bound or compute-bound. This is not a replacement for profiling but a prerequisite for interpreting profiling data correctly.
The second change is the internalization of the cost model. The latency ratios — register access: ~0 cycles, L1: 4 cycles, L2: 12 cycles, L3: 40 cycles, main memory: 200+ cycles, disk: 10⁶ cycles, network: 10⁸ cycles — are the numbers that determine which optimizations matter and which do not. A function that executes ten arithmetic instructions per cache miss is memory-bound; adding more arithmetic does not help. A function that executes one arithmetic instruction per data element is compute-bound; better cache behavior does not help. Getting the diagnosis right requires the cost model; getting the cost model requires understanding the hierarchy.
The third change is the ability to read assembly. Modern compilers are sophisticated, and the code they generate does not always match programmer expectations. Reading the assembly output of a critical section — seeing whether the loop has been vectorized, whether a critical value is kept in a register or reloaded from memory, whether a function call has been inlined — is the only way to verify that the program is doing what you think it is doing at the hardware level. This ability is rarely needed for routine programming but is essential for performance-critical work.
The fourth change is security awareness at the hardware level. Spectre, Meltdown, and the family of transient-execution attacks are only comprehensible to practitioners who understand speculative execution and cache-based side channels. Knowing that a colocated process can potentially observe your memory access patterns through timing differences, or that indirect branches can be trained by an adversary to speculatively execute wrong paths — and knowing which mitigations (IBRS, KPTI, retpolines) address which attacks at what performance cost — requires the microarchitectural picture. These are not exotic concerns; they are active mitigations applied to every major operating system and cloud platform.
Resources
Books and Texts
Bryant and O’Hallaron’s Computer Systems: A Programmer’s Perspective (CSAPP, 3rd ed., Pearson, 2016) is the right starting point for the overwhelming majority of CS practitioners. Uniquely among architecture texts, it is written from the programmer’s perspective: how do architectural choices affect what programs can do, how fast they run, and what bugs they can exhibit? The memory hierarchy chapters are the best available treatment of cache effects on software performance. The security chapters cover buffer overflows and injection attacks in the context of how programs are laid out in memory. The concurrent programming chapter covers threads and synchronization with attention to the memory model the hardware provides. The associated lab assignments — the bomb lab (reverse engineering a binary), the buffer lab (exploiting a stack overflow), the architecture lab (extending a processor simulator), the cache lab (writing cache-aware code) — are genuinely educational and widely used across university programs. For the practitioner who needs to understand hardware well enough to write effective systems software, CSAPP is the canonical text.
Patterson and Hennessy’s Computer Organization and Design (5th ed., RISC-V edition, 2021) provides the hardware-design perspective that CSAPP covers less deeply. Where CSAPP explains how to write cache-aware code, Patterson-Hennessy explains how caches work — the structure of the cache, the replacement policies, the write policies, the implementation. Where CSAPP covers pipelines from the software side, Patterson-Hennessy covers the pipeline from the hardware side, including the design of the hazard detection and forwarding logic. The RISC-V edition uses the open instruction set that is increasingly standard in education and in new designs; earlier MIPS and ARM editions remain useful but the RISC-V edition is current. For practitioners who want to understand hardware deeply enough to reason about implementation choices, or who plan to work on compilers or low-level systems software, Patterson-Hennessy is the appropriate companion to CSAPP.
For advanced microarchitecture, Hennessy and Patterson’s Computer Architecture: A Quantitative Approach (6th ed., 2019) is the graduate-level reference. The topics CSAPP and Patterson-Hennessy treat briefly — out-of-order execution, memory consistency models, GPU architecture, domain-specific accelerators — are treated at depth here with the quantitative analysis that characterizes serious architectural work. The textbook is updated with each edition to include contemporary architectures; the sixth edition includes substantial treatment of neural network accelerators and warehouse-scale computing. For practitioners who need to understand why contemporary processors make the design choices they do, or who are planning research in computer architecture, this is the essential reference.
Drepper’s What Every Programmer Should Know About Memory (long technical paper, free online, 2007) extends the memory hierarchy content of CSAPP with more depth. It covers the physical structure of DRAM and cache, NUMA topology, prefetching, and cache-conscious data structure design. Parts of the specific microarchitectural advice are dated, but the analysis of why the memory hierarchy works the way it does, and what software patterns best exploit it, remains accurate and essential. It is the right reading for any practitioner who works on performance-sensitive code that operates on large data.
| Book | Role | Type |
|---|---|---|
| Bryant & O’Hallaron, Computer Systems: A Programmer’s Perspective (3rd ed.) | Canonical entry for software practitioners | Entry |
| Patterson & Hennessy, Computer Organization and Design (RISC-V ed.) | Hardware-oriented entry | Entry |
| Hennessy & Patterson, Computer Architecture: A Quantitative Approach (6th ed.) | Graduate-level reference | Reference |
| Drepper, What Every Programmer Should Know About Memory (free) | Memory hierarchy at depth | Depth |
| Sorin, Hill & Wood, A Primer on Memory Consistency and Cache Coherence (paid / library) | Memory consistency and cache coherence at depth | Depth |
Courses and Lectures
CMU 15-213 (Introduction to Computer Systems, freely available with full lecture videos and lab assignments) is the course that CSAPP was designed for. The labs — bomb lab, buffer overflow lab, architecture lab, cache lab, shell lab, proxy lab — constitute a practitioner education in systems programming that textbook reading alone cannot provide. Working through the CMU labs is the most productive single investment for a programmer seeking to understand systems. Berkeley CS 61C (Great Ideas in Computer Architecture, lectures and materials freely available) covers similar territory with more emphasis on hardware and a Berkeley RISC-V toolchain.
MIT 6.004 (Computation Structures, materials on MIT OCW and as an edX course) builds from basic digital logic through processor design and operating system interfaces, providing the hardware-design perspective that CSAPP leaves to Patterson-Hennessy. It is more oriented toward understanding the hardware than toward writing efficient software, and it complements CSAPP rather than duplicating it.
| Course | Platform | Type |
|---|---|---|
| CMU 15-213 Introduction to Computer Systems (free) | CMU / YouTube | Entry |
| Berkeley CS 61C Computer Architecture (free) | Berkeley / YouTube | Entry |
| MIT 6.004 Computation Structures (free) | MIT OCW / edX | Depth |
Practice, Tools, and Current Sources
Godbolt Compiler Explorer (godbolt.org, free) shows the assembly generated by compilers for code you type, with annotations linking source lines to assembly. It is the most practical tool for understanding what compilers do with your code, what the ISA looks like at the instruction level, and how architectural features like SIMD vectorization manifest in real code. Paste a loop and check whether GCC or Clang has vectorized it; understand why the assembly looks different with -O2 versus -O3.
Cachegrind (part of Valgrind, free) simulates cache behavior and counts cache misses. Profiling a program with Cachegrind identifies which functions and data structures cause the most cache misses — directly actionable information for memory hierarchy optimization. perf (Linux, free) samples hardware performance counters on real hardware, including actual cache miss counts, branch mispredictions, and IPC (instructions per clock).
Implementing a simple pipelined processor simulator — even a five-stage RISC-V pipeline with hazard detection and forwarding — is the most effective way to understand why code runs the speed it does. The Patterson-Hennessy lab infrastructure supports this; several freely available RISC-V simulators (Spike, RARS) support instruction-level debugging. The experience of implementing a pipeline, then observing how code runs differently than expected because of hazards, internalizes the microarchitectural picture in a way that reading cannot.
Agner Fog’s optimization manuals (agner.org, free) are the most detailed publicly available documentation of specific Intel and AMD microarchitectures — instruction latencies, throughput, decoder widths, branch predictor behavior. For performance-critical code targeting specific processors, these manuals are indispensable references.
| Resource | Platform | Type |
|---|---|---|
| Godbolt Compiler Explorer (free) | godbolt.org | Practice |
| Cachegrind / Valgrind (free) | valgrind.org | Practice |
| perf (Linux, free) | Linux kernel tools | Practice |
| Agner Fog optimization manuals (free) | agner.org | Reference |
| uops.info instruction tables (free) | uops.info | Reference |
| Intel 64 and IA-32 Optimization Reference Manual (free) | intel.com | Reference |
| Chips and Cheese microarchitecture analyses (free) | chipsandcheese.com | Auxiliary |
| RISC-V pipeline simulator project | Local | Practice |
Traps
| Trap | Why it misleads | Better response |
|---|---|---|
| Spending too long on gate-level digital design | Boolean algebra, combinational circuit design, and sequential circuit design are necessary foundations but are not where architecture’s interesting problems live. A learner who completes a digital design course and believes they have studied computer architecture has acquired the entry vocabulary without the subject. The memory hierarchy, out-of-order execution, branch prediction, and parallelism are where the intellectual content is. | Treat gate-level material as a two-week foundation, not as a semester’s study. Allocate the majority of architectural study time to the memory hierarchy, microarchitecture, and parallelism topics that directly affect software performance. The CSAPP sequencing reflects this priority correctly. |
| Treating the ISA as the architecture | The ISA — the set of instructions a processor can execute — is the contract between hardware and software, not the architecture itself. The architectural techniques that determine performance (caching, speculation, out-of-order execution) are microarchitectural, not ISA-level. Two processors with identical ISAs but different microarchitectures can perform identically differently on the same code. | Study microarchitecture, not just the instruction set. Understanding what specific instructions do is much less important than understanding how the processor executes them — which instructions can issue together, what causes pipeline stalls, how memory access patterns affect cache behavior. |
| Ignoring the memory hierarchy | The memory hierarchy is the single most important performance factor for most real workloads. A function that fits in L1 cache runs ten times faster than the same function that causes frequent main-memory accesses. But most introductory treatments allocate proportional space to memory and execution, rather than reflecting memory’s actual dominance. | Spend disproportionate time on the memory hierarchy. Understand the latency ratios by heart. Implement the matrix multiplication tiling exercise in CSAPP and measure the performance difference. The cache lab in CMU 15-213 is the most direct way to develop memory-hierarchy intuition. |
| Skipping assembly because “compilers handle it” | Assembly is the view from below the abstraction. A compiler might vectorize your loop or might not, might keep a value in a register or reload it, might inline a function or call it. The only way to verify that the compiler did what you intended is to read the output. For performance-critical code, this verification is routine; for debugging unusual behavior, it is sometimes the only path to the answer. | Spend ten hours deliberately reading assembly output on Godbolt. Take a simple loop, compile it with optimization, and understand every instruction. Then add a complexity (a branch, a pointer indirection) and observe how the assembly changes. The investment is small relative to the clarity it provides. |
| Missing the security implications of microarchitecture | Spectre, Meltdown, and the broader class of transient-execution attacks are the most significant security vulnerabilities of the past decade and are completely opaque without understanding speculative execution and cache-based side channels. Practitioners who treat security as a separate subject from architecture miss the source of these vulnerabilities. | Read the original Spectre and Meltdown papers (Kocher et al. 2018, Lipp et al. 2018) after completing the CSAPP pipelining and memory hierarchy chapters. The papers are written for practitioners with exactly the background CSAPP provides, and they demonstrate concretely how microarchitectural optimizations become security vulnerabilities. |
| Treating architecture as solved and static | The architectural assumptions that were standard in 2010 — single-threaded performance will keep improving, workloads are general-purpose, the x86 ISA is effectively universal — have all shifted. Specialized accelerators, the RISC-V open ISA, the security implications of speculative execution, and the emergence of persistent memory are reshaping the field. Textbook knowledge represents what was true at printing time, not what is true now. | Follow contemporary architecture through the processor trade press (Chips and Cheese, Real World Technologies) and the papers from ISCA, HPCA, MICRO, and ASPLOS. The Hennessy-Patterson Turing lecture “A New Golden Age for Computer Architecture” (2017, free) provides the best short account of why the field is actively changing. |
中文
处理器是一台执行指令的机器。这句话是真的,但几乎完全没有信息量——就像说汽车是一台会移动的机器。真正有趣的问题是:如何执行,执行得多快,代价是什么。现代处理器每秒可以执行数百亿条指令,但任何单条指令到达执行单元可能只需要约一纳秒;这两者之间的差距,由一整套复杂技术填补——流水线、乱序执行、分支预测、推测执行——这些技术允许处理器同时让许多指令处在执行途中。理解这些技术是什么、什么时候有帮助、什么时候会引入问题,就是计算机体系结构要研究的内容。
这个主题有三个层次,值得明确区分。最底层是数字抽象:也就是这样一种约定——物理电学现象可以被当作离散的二进制信号处理,从而让电路可以用布尔逻辑而不是模拟电子学来设计。这种虚构被工程化到可以在容差范围内成立,而现代处理器维持它的可靠程度足以让软件看不见背后的模拟现实。指令集架构(instruction-set architecture,ISA)是硬件与软件之间的正式契约:它完整规定处理器可以执行哪些指令,以及这些指令做什么。软件面向 ISA 编写;硬件实现 ISA。ISA 被设计为具有几十年的稳定性——1980 年代的 x86 代码今天仍然能在当前 Intel 处理器上正确运行。微架构则是 ISA 之下的实现:实际的晶体管、流水线和缓存层级如何实现 ISA 契约。微架构随每一代处理器改变,是专有的,通常也不会完整公开;它也是体系结构中大多数有趣工程发生的层次。
这个主题位于系统轴的基础位置。操作系统(§4.2)依赖硬件机制——特权模式、内存管理单元、中断、原子指令——这些机制由 ISA 定义。编译器(§4.7)把程序翻译成指令序列,而微架构会根据编译器对目标机器理解得好坏,高效或低效地执行这些序列。每一个下游主题中的每一次性能测量,最终都是硬件如何执行代码的结果。把硬件当作黑箱的实践者,会积累不可见的成本:错失性能,误诊 bug,以及在没有微架构图景时不可见的安全漏洞。
前置知识:编程(§2.1)——你需要先知道程序做什么,才能理解硬件如何执行它们。离散数学(§2.2)——布尔逻辑、数值表示,以及对状态机的推理。算法(§2.6)——当你理解成本来自哪里时,成本分析才会变得具体。
从继电器计算器到推测执行
机械计算器在电子计算机之前已经存在了几个世纪,但电子存储程序计算机——指令和数据共同存在同一内存中,机器自动读取并执行它们——是 1940 年代的一项具体发明,而它的体系结构组织了此后几乎所有计算。
这个概念设计通常归功于 John von Neumann 1945 年描述 EDVAC 的草案报告,尽管这一设计是他与 J. Presper Eckert、John Mauchly 等人共同发展出来的。von Neumann 架构把中央处理器(CPU)与主内存分离:CPU 从内存中取出一条指令,对其解码,执行它,写回结果,然后重复。这一取指—解码—执行循环至今仍是理解处理器如何工作的概念模型,尽管现代处理器的物理现实几乎在每一个细节上都偏离了它。这个模型的简单性——一次一条指令,按顺序执行,内存均匀——使早期硬件容易实现,也使程序容易推理。也正是这些简化假设,变成了性能障碍,而四十年的微架构创新一直在努力克服它们。
第一项主要性能技术是流水线,它借鉴自工业装配线。如果执行可以被拆分为多个阶段——取指、解码、执行、写回——并且不同指令可以同时占据不同阶段,那么吞吐量就会提高,尽管单条指令并不会完成得更快。IBM Stretch(1961)是最早对指令执行进行流水线化的计算机之一。Intel 8086(1978)使用了简单的两级流水线。到 1990 年代,十到二十级的深流水线已经很常见,因为每一级执行的工作更少,从而允许更高时钟频率。流水线的代价是复杂性:存在数据依赖的指令必须停顿,等待前面指令的结果;分支会扰乱流水线,因为处理器在分支执行前并不知道下一条该取哪条指令;异常要求回退已经部分执行的指令。管理这些 hazard——数据 hazard、控制 hazard、结构 hazard——是流水线设计的核心问题。
1980 年代的 RISC 与 CISC 之争定义了一代体系结构思维。复杂指令集计算机(Complex Instruction Set Computers,CISC)——Intel 的 x86、DEC 的 VAX、Motorola 68000——提供丰富指令集,把常见操作编码为单条复杂指令,从而减少程序所需指令数量。支持 CISC 的论点是:更少指令意味着更快程序,尤其是在内存昂贵、代码密度重要时。精简指令集计算机(Reduced Instruction Set Computers,RISC)——Berkeley Patterson 及其同事的 MIPS、Sun 的 SPARC、IBM 801——提供简单指令,这些指令可以在可预测的单周期中执行,并且容易流水线化。David Patterson 和 John Hennessy 在 1980 年代早期的研究经验证明,对于典型编译器生成代码,CISC 程序实际并不比等价 RISC 程序执行更少指令,而简单流水线化 RISC 设计会超过当时复杂 CISC 设计。RISC 哲学——load/store 架构、定长指令、大寄存器文件、避免隐式状态——成为主导设计方法。几乎每部智能手机中的 ARM 架构是 RISC。正在学术界和嵌入式系统中被采用的开放架构 RISC-V 是 RISC。即便名义上是 CISC 的 x86,内部也会被执行引擎解码为类似 RISC 的 micro-operations。
1990 年代带来了超标量执行:通过检测彼此独立的指令,并行发射多条指令,使每个时钟周期可以发出多条指令。这要求乱序执行——只要符合数据依赖关系,就允许指令按任意顺序执行,而不必按它们在程序中出现的顺序执行。Tomasulo 算法最初为 1967 年的 IBM 360/91 开发,但直到 1990 年代才被广泛采用,它提供了标准机制:指令被发射到队列中,操作数追踪硬件检测每条指令的输入何时可用,而指令一旦操作数就绪就执行。结合寄存器重命名——维护逻辑寄存器的多个物理副本,以消除虚假依赖——乱序处理器即使在程序代码存在许多潜在 hazard 时,也能维持高指令吞吐量。
当深流水线与超标量执行结合时,分支预测变得关键。现代处理器如果有二十级流水线,那么在分支指令真正执行时,它之后二十多条指令可能已经开始被推测执行。如果预测错误,所有这些指令都必须被丢弃,并从正确路径重新开始执行——代价可能是二十个周期甚至更多。分支预测器从简单静态启发式发展而来(向后分支通常 taken,向前分支通常 not taken——分别对应循环和条件语句的启发式),经历了追踪单个分支历史的二位计数器、结合多种预测方案的 tournament predictors,最终发展到使用类似 perceptron 结构的现代神经预测器。当前 Intel 和 AMD 处理器在多数工作负载上可以达到 95% 以上的预测准确率——考虑到预测必须在流水线第一阶段做出,而分支要很久之后才解析,这很惊人。
时钟频率时代大约在 2005 年结束。从 1970 年代到 2000 年代早期,处理器时钟频率大约每两年翻倍——二十年间从 MHz 发展到 GHz。这种改进来自制造进步(更小晶体管、更低电容),使开关速度更快。但时钟频率、电压和功耗之间的物理关系,为进一步提高时钟频率设置了天花板:更高时钟频率需要更高电压来保证可靠性,而功耗大约随电压立方增长。到 2005 年,继续推动时钟频率需要超出现实工程能力的冷却系统。Intel 曾把 NetBurst 微架构推到 4 GHz,但后来放弃它,转向更低功耗、更低时钟的 Core 架构。依靠时钟缩放免费获得性能提升的时代结束了。
回应方式是多核:在单个芯片上放置多个处理器核心,并共享缓存层级和内存控制器。一个四核 2 GHz 芯片,每秒执行的操作量大致相当于一个 8 GHz 单核,但功耗显著更低,也不会撞上热限制。从 2005 年以后,通用处理器的大多数性能改进来自增加核心数量,而不是提高时钟频率。消费级处理器从双核发展到四核、八核、十六核乃至更多;服务器处理器现在常常有六十四个或更多核心。随之而来的挑战——如何编写有效利用这种并行性的程序——是计算领域最持久的未解决问题之一,而大多数应用软件仍然只能部分利用可用并行性。
2018 年 1 月发现的 Spectre 和 Meltdown,是几十年来最重要的体系结构安全事件。二者都利用了 ISA 规范与微架构现实之间的差距。ISA 规定,一个进程只能访问自己的内存;而微架构为了追求性能,会从禁止访问的内存区域推测执行指令,并用结果影响缓存状态,尽管这些推测结果永远不会被提交。攻击者可以通过观察缓存访问中的时间差异来推断被禁止访问的内存内容——这是一种侧信道,ISA 的访问控制无法防止它,因为它发生在微架构层面。Meltdown 利用了 Intel 处理器中的一个特定优化,主要通过软件缓解。Spectre 更广泛,利用的是推测执行的基本行为,且在不付出显著性能成本的情况下更难缓解。这些漏洞揭示了四十年中积累的性能优化已经制造出安全风险,而 ISA 规范没有处理这些风险,微架构也没有考虑它们。此后,体系结构共同体投入大量努力发展对 confidential computing 的体系结构支持——包括 Arm 的 Confidential Compute Architecture 和 Intel 的 TDX——试图提供由硬件强制执行、并且能抵抗微架构侧信道的安全边界。
当代图景由专用化定义。通用处理器的单线程性能已经趋于平台化;进一步提升需要专用硬件。GPU 最初为图形渲染设计,现在通过在大量核心上并行执行成千上万次操作,加速机器学习所需的稠密矩阵运算。Google 的 TPU(Tensor Processing Units)进一步专用化,专门优化其搜索和翻译基础设施核心中的神经网络计算。每部现代 iPhone 中的 Apple Neural Engine,也提供类似的专用推理加速。从通用硬件转向专用硬件,是主导趋势;体系结构挑战在于提供软件抽象——编程模型、编译器、运行时系统——让程序员能够面向异构硬件,而不必为每个 accelerator 重写代码。
内存层级、性能与计算成本
内存:主导性瓶颈
处理器与内存的关系,是当代计算机体系结构中的核心性能问题。一个现代高性能核心可以在 3–5 GHz 时钟频率下,每个时钟周期执行数条指令;访问主内存大约需要 60–100 纳秒——也就是 200–500 个时钟周期。如果每条指令都需要访问主内存,处理器会有 99% 的时间处于空闲状态。处理器很快;内存很慢;二者之间的差距很大,而且已经扩大了几十年。
解决方案是内存层级:一组逐级更大、也逐级更慢的存储层,每一层都作为下一层的缓存。寄存器保存少量值,可以在不到一个周期内访问。L1 cache 保存数十 KB,可在 4–5 个周期内访问。L2 cache 保存数百 KB,可在 12–15 个周期内访问。L3 cache 保存数十 MB,可在 30–50 个周期内访问。主内存保存 GB 级数据,可在 200–500 个周期内访问。当所需值在 L1 cache 中——cache hit——执行会快速继续。当不在其中——cache miss——执行会停顿,等待 cache 从下一层取回该值。每一层访问命中的比例——hit rate——决定性能。
内存层级之所以有效,是因为局部性:真实程序同时表现出时间局部性(最近访问的数据很快会再次访问)和空间局部性(最近访问数据附近的数据很快会被访问)。Caches 通过把最近访问数据保存在快速存储中来利用时间局部性。Cache lines——通常是 64 bytes——通过不仅取回请求值,也一起取回周围字节来利用空间局部性,使后续访问相邻数据几乎免费。
Cache misses 是多数真实软件的主导性能问题。矩阵乘法、大数组遍历和随机访问数据结构,各自具有不同 cache 行为。朴素编码的矩阵乘法会对其中一个操作数按列访问,对于大矩阵,这几乎会在每次访问时造成 cache miss;cache-blocked(或 tiled)实现会访问能放入 cache 的小块,从而把 misses 降低一个数量级。链表遍历会跟随散布在内存中的指针,几乎每访问一个节点就产生一次 cache miss;基于数组的替代方案可以避免这一点,但要求不同算法结构。使用良好哈希函数的哈希表查找会产生可预测的内存访问模式;而使用差哈希函数或高负载因子时,则会产生昂贵的 cache misses。
NUMA(Non-Uniform Memory Access)架构在多处理器系统中进一步复杂化了内存层级:每个处理器都有本地内存,可以快速访问;也有远程内存,也就是附着在其他处理器上的内存,访问更慢。不了解 NUMA 的软件可能会把数据放在访问它的处理器的远程内存中,浪费大量带宽并增加延迟。
流水线与执行:性能被创造和丢失的地方
现代处理器通过流水线、乱序执行和超标量 dispatch 来利用指令级并行(instruction-level parallelism,ILP)。理解什么时候 ILP 可以被利用,什么时候不能,决定哪些代码模式更快。
数据依赖限制 ILP。如果指令 B 需要指令 A 的输出,那么 B 必须等 A 完成后才能 issue。一串相互依赖的加法,即使在超标量处理器上,也受限于每个时钟周期只能执行一次,因为每次加法都依赖前一次。独立操作可以同时 issue:在双路超标量处理器上,并行计算 a + b 和 c + d 所需时间是顺序计算的一半。
分支预测错误会浪费流水线吞吐量。当一个错误预测被检测到——通常是在流水线末端——所有在分支之后 issue 的指令都必须被 flush,并从正确路径重新开始执行。对于二十级流水线,一次错误预测会损失二十个周期。包含不可预测分支的代码——也就是分支结果无法由局部历史判断的分支——明显慢于包含可预测分支的代码。这就是为什么在对数组执行条件操作之前先排序,有时会比直接对未排序数组执行操作更快,尽管排序需要额外工作:排序后的数据使分支变得可预测。
内存级并行(memory-level parallelism,MLP)允许多个 cache misses 同时处在进行中。硬件 prefetchers 会检测顺序或固定步长访问模式,并在数据被请求前提前取回。软件 prefetch instructions 允许编译器或程序员显式请求数据。对于顺序访问内存的工作负载——扫描大数组——硬件 prefetchers 效果很好,可以有效隐藏内存延迟。对于具有不规则访问模式的工作负载——链式结构中的 pointer chasing、对大型哈希表的随机访问——prefetching 无效,每次 cache miss 都会导致执行停顿。
并行性:核心、向量与 Accelerators
来自 ILP 的单核性能改进已经基本平台化;进一步性能提升来自更大尺度上的并行性。
线程级并行(thread-level parallelism,TLP)使用多个核心执行独立线程。共享内存多处理器需要 coherence:当一个核心写入某个位置时,其他核心必须看到更新后的值,而不是旧的缓存副本。Cache coherence protocols——MESI(Modified, Exclusive, Shared, Invalid)是标准——通过追踪每条 cache line 在所有处理器中的状态来维持一致性。False sharing 出现在两个核心反复修改恰好共享同一条 cache line 的不同变量时;coherence protocol 会强制这条 cache line 以 cache-miss 成本在处理器之间来回移动,尽管并不存在真正共享。False sharing 是并发代码中的常见性能 bug。
向量指令会同时对多个值执行同一操作。x86 上的 SSE、AVX 和 AVX-512;ARM 上的 NEON 和 SVE;都提供 SIMD(Single Instruction Multiple Data)执行。一条作用于 32-bit floats 的 256-bit AVX2 指令,每条指令可以处理八个值。结构良好的数值代码——矩阵乘法、卷积、点积的内层循环——可以由编译器或显式 intrinsics 向量化,在兼容硬件上获得显著加速。带有复杂控制流或 gather/scatter 内存访问模式的非结构化代码,则很难向量化。
GPU 以限制性编程模型为代价,提供大规模并行性。现代 GPU 包含数千个 shader processors,被组织成 streaming multiprocessors(SMs),每个 SM 同时执行一个由 32 个 threads 组成的 warp。GPU 在数据并行工作负载上达到高吞吐量——矩阵乘法、卷积、element-wise operations——其中同一计算可以独立应用到成千上万个数据点上。它们不擅长不规则控制流、小 batch sizes,或需要线程之间频繁通信的工作。CUDA 编程模型(NVIDIA)及其替代方案(ROCm、Metal、SYCL)把这种并行性暴露给程序员;理解目标 GPU 的 threading model 和内存层级,对于获得高性能至关重要。
学习这一部分会改变什么
计算机体系结构会改变实践者在运行中代码里能看见什么。
第一个变化,是能够从物理第一性原理推理性能。Cache misses、branch mispredictions 和 pipeline stalls 不是神秘的性能退化;它们是微架构结构的可预测后果。理解内存层级的实践者,可以看着一个数据结构,预测对它的访问会命中 L1、L2 还是主内存;可以看着一个循环,估计每次迭代需要多少周期;可以判断一个性能问题是 memory-bound 还是 compute-bound。这并不是 profiling 的替代品,而是正确解释 profiling 数据的前提。
第二个变化,是内化成本模型。延迟比例——寄存器访问:约 0 cycles,L1:4 cycles,L2:12 cycles,L3:40 cycles,主内存:200+ cycles,磁盘:10⁶ cycles,网络:10⁸ cycles——决定了哪些优化重要,哪些不重要。一个每次 cache miss 只执行十条算术指令的函数是 memory-bound;增加更多算术没有帮助。一个每个数据元素执行一条算术指令的函数是 compute-bound;更好的 cache 行为没有帮助。诊断正确需要成本模型;获得成本模型需要理解层级结构。
第三个变化,是阅读汇编的能力。现代编译器很复杂,它们生成的代码并不总是符合程序员预期。阅读关键部分的汇编输出——看循环是否被向量化,看关键值是保存在寄存器里还是从内存重新加载,看函数调用是否被 inline——是验证程序在硬件层面是否正在做你以为它在做的事情的唯一方式。这种能力在日常编程中很少需要,但对性能关键工作至关重要。
第四个变化,是硬件层面的安全意识。Spectre、Meltdown 和 transient-execution attacks 家族,只有理解推测执行和基于 cache 的侧信道的实践者才能真正理解。知道一个 colocated process 可能通过时间差异观察你的内存访问模式,或知道间接分支可能被攻击者训练到推测执行错误路径——并且知道哪些缓解措施(IBRS、KPTI、retpolines)处理哪些攻击、代价是什么——都需要微架构图景。这些不是异域问题;它们是每个主要操作系统和云平台都在主动应用的缓解措施。
资源
书籍与文本
Bryant 和 O’Hallaron 的 Computer Systems: A Programmer’s Perspective(CSAPP,第 3 版,Pearson,2016)是绝大多数 CS 实践者的正确起点。在体系结构教材中,它独特地从程序员视角写作:体系结构选择如何影响程序能做什么、运行多快,以及可能表现出什么 bug?其中内存层级章节,是目前关于 cache effect 对软件性能影响的最佳处理。安全章节把 buffer overflows 和 injection attacks 放在程序如何在内存中布局的语境中解释。并发编程章节则以硬件提供的内存模型为背景处理 threads 和 synchronization。配套实验——bomb lab(二进制逆向工程)、buffer lab(利用 stack overflow)、architecture lab(扩展处理器模拟器)、cache lab(编写 cache-aware 代码)——真正具有教育价值,并被许多大学项目广泛使用。对于需要足够理解硬件以编写有效系统软件的实践者,CSAPP 是经典文本。
Patterson 和 Hennessy 的 Computer Organization and Design(第 5 版,RISC-V edition,2021)提供 CSAPP 较少深入处理的硬件设计视角。CSAPP 解释如何编写 cache-aware code,而 Patterson-Hennessy 解释 caches 如何工作——cache 结构、替换策略、写策略及其实现。CSAPP 从软件侧覆盖流水线,Patterson-Hennessy 则从硬件侧覆盖流水线,包括 hazard detection 和 forwarding logic 的设计。RISC-V 版本使用开放指令集,这一指令集正越来越成为教育和新设计中的标准;早期 MIPS 和 ARM 版本仍然有用,但 RISC-V 版更符合当前。对于想要深入理解硬件,以便推理实现选择,或计划从事编译器、底层系统软件工作的实践者来说,Patterson-Hennessy 是 CSAPP 的合适配套文本。
对于高级微架构,Hennessy 和 Patterson 的 Computer Architecture: A Quantitative Approach(第 6 版,2019)是研究生层级参考。CSAPP 和 Patterson-Hennessy 简要处理的主题——乱序执行、内存一致性模型、GPU 架构、领域专用 accelerators——在这里会以严肃体系结构工作所需的定量分析深入展开。这本教材每一版都会更新,以纳入当代架构;第六版包含了对神经网络 accelerators 和 warehouse-scale computing 的大量处理。对于需要理解为什么当代处理器会做出这些设计选择的实践者,或计划进行计算机体系结构研究的人来说,这是必要参考。
Drepper 的 What Every Programmer Should Know About Memory(免费在线长篇技术论文,2007)在更深层次上扩展了 CSAPP 的内存层级内容。它覆盖 DRAM 和 cache 的物理结构、NUMA 拓扑、prefetching,以及 cache-conscious 数据结构设计。部分具体微架构建议已经过时,但它关于内存层级为什么如此工作,以及哪些软件模式最能利用它的分析,仍然准确且必要。对于任何处理大型数据、编写性能敏感代码的实践者来说,它是正确读物。
| 书籍 | 作用 | 类型 |
|---|---|---|
| Bryant & O’Hallaron, Computer Systems: A Programmer’s Perspective(第 3 版) | 面向软件实践者的经典入口 | 入门 |
| Patterson & Hennessy, Computer Organization and Design(RISC-V ed.) | 硬件导向入口 | 入门 |
| Hennessy & Patterson, Computer Architecture: A Quantitative Approach(第 6 版) | 研究生层级参考 | 参考 |
| Drepper, What Every Programmer Should Know About Memory(免费) | 深入内存层级 | 深入 |
| Sorin, Hill & Wood, A Primer on Memory Consistency and Cache Coherence(付费 / 图书馆) | 深入内存一致性与 cache coherence | 深入 |
课程与讲座
CMU 15-213(Introduction to Computer Systems,完整讲座视频和实验任务免费可得)是 CSAPP 被设计来配套的课程。其中实验——bomb lab、buffer overflow lab、architecture lab、cache lab、shell lab、proxy lab——构成了一套系统编程实践者教育,单靠阅读教材无法替代。完成 CMU 实验,是程序员理解系统最有产出的单项投入。Berkeley CS 61C(Great Ideas in Computer Architecture,讲座和材料免费可得)覆盖类似内容,但更强调硬件和 Berkeley RISC-V 工具链。
MIT 6.004(Computation Structures,MIT OCW 和 edX 上有材料)从基础数字逻辑讲到处理器设计和操作系统接口,提供 CSAPP 留给 Patterson-Hennessy 的硬件设计视角。它更偏向理解硬件,而不是编写高效软件;它补充 CSAPP,而不是重复它。
| 课程 | 平台 | 类型 |
|---|---|---|
| CMU 15-213 Introduction to Computer Systems(免费) | CMU / YouTube | 入门 |
| Berkeley CS 61C Computer Architecture(免费) | Berkeley / YouTube | 入门 |
| MIT 6.004 Computation Structures(免费) | MIT OCW / edX | 深入 |
实践、工具与当前资料
Godbolt Compiler Explorer(godbolt.org,免费)会显示编译器为你输入的代码生成的汇编,并用注释把源代码行连接到汇编。它是理解编译器如何处理代码、ISA 在指令层面是什么样、以及 SIMD vectorization 这类体系结构特性如何在真实代码中体现的最实用工具。粘贴一个循环,检查 GCC 或 Clang 是否把它向量化;理解为什么 -O2 和 -O3 下汇编会不同。
Cachegrind(Valgrind 的一部分,免费)会模拟 cache 行为并统计 cache misses。用 Cachegrind 分析程序,可以识别哪些函数和数据结构造成最多 cache misses——这是对内存层级优化直接可操作的信息。perf(Linux,免费)会在真实硬件上采样硬件性能计数器,包括真实 cache miss counts、branch mispredictions 和 IPC(instructions per clock)。
实现一个简单流水线处理器模拟器——即使只是一个带 hazard detection 和 forwarding 的五级 RISC-V 流水线——是理解代码为什么以某种速度运行的最有效方式。Patterson-Hennessy 的实验基础设施支持这一点;几个免费 RISC-V 模拟器(Spike、RARS)支持指令级调试。实现流水线,然后观察代码如何因为 hazards 而以不同于预期的方式运行,会以内化方式建立微架构图景,这是阅读无法提供的。
Agner Fog 的优化手册(agner.org,免费)是关于特定 Intel 和 AMD 微架构最详细的公开文档——包括指令延迟、吞吐量、decoder widths、branch predictor behavior。对于针对特定处理器的性能关键代码,这些手册是不可或缺的参考。
| 资源 | 平台 | 类型 |
|---|---|---|
| Godbolt Compiler Explorer(免费) | godbolt.org | 实践 |
| Cachegrind / Valgrind(免费) | valgrind.org | 实践 |
| perf(Linux,免费) | Linux kernel tools | 实践 |
| Agner Fog optimization manuals(免费) | agner.org | 参考 |
| uops.info instruction tables(免费) | uops.info | 参考 |
| Intel 64 and IA-32 Optimization Reference Manual(免费) | intel.com | 参考 |
| Chips and Cheese microarchitecture analyses(免费) | chipsandcheese.com | 辅助 |
| RISC-V pipeline simulator project | 本地 | 实践 |
陷阱
| 陷阱 | 为什么会误导 | 更好的回应 |
|---|---|---|
| 在 gate-level digital design 上花太久 | 布尔代数、组合电路设计和时序电路设计是必要基础,但它们不是体系结构中真正有趣的问题所在。一个学习者完成一门数字设计课程后,以为自己已经学习了计算机体系结构,其实只是获得了入门词汇,而不是这门学科本身。内存层级、乱序执行、分支预测和并行性,才是智识内容所在。 | 把 gate-level 内容当作两周基础,而不是一个学期的主体。把体系结构学习的大部分时间分配给内存层级、微架构和并行性主题,因为它们直接影响软件性能。CSAPP 的编排正确体现了这一优先级。 |
| 把 ISA 当成体系结构本身 | ISA——处理器可以执行的指令集合——是硬件和软件之间的契约,不是体系结构本身。决定性能的体系结构技术(缓存、推测、乱序执行)属于微架构,而不是 ISA 层。两个 ISA 完全相同、但微架构不同的处理器,可能在同一代码上表现出截然不同的性能。 | 学习微架构,而不只是指令集。理解具体指令做什么,远不如理解处理器如何执行它们重要——哪些指令可以同时 issue,什么会造成 pipeline stalls,内存访问模式如何影响 cache 行为。 |
| 忽视内存层级 | 内存层级是大多数真实工作负载中最重要的单一性能因素。一个能放入 L1 cache 的函数,比同一个频繁访问主内存的函数快十倍。但多数入门处理会把内存与执行分配相近篇幅,而没有反映内存在实际中的支配地位。 | 对内存层级投入不成比例的大量时间。把延迟比例背下来。完成 CSAPP 中的矩阵乘法 tiling 练习,并测量性能差异。CMU 15-213 的 cache lab 是发展内存层级直觉最直接的方式。 |
| 因为“编译器会处理”而跳过汇编 | 汇编是从抽象之下观察程序的视角。编译器可能会向量化你的循环,也可能不会;可能把一个值保留在寄存器里,也可能重新加载;可能 inline 一个函数,也可能调用它。验证编译器是否做了你预期的事情,唯一方式就是阅读输出。对于性能关键代码,这种验证是常规操作;对于调试异常行为,它有时是唯一通路。 | 专门花十小时在 Godbolt 上阅读汇编输出。取一个简单循环,开启优化编译,并理解每条指令。然后增加复杂性(一个分支、一次指针间接访问),观察汇编如何变化。相对于它提供的清晰度,这个投入很小。 |
| 看不见微架构的安全含义 | Spectre、Meltdown 以及更广泛的 transient-execution attacks,是过去十年中最重要的安全漏洞,但如果不理解推测执行和基于 cache 的侧信道,它们完全不可见。把安全当成与体系结构分离主题的实践者,会错过这些漏洞的来源。 | 完成 CSAPP 的流水线和内存层级章节之后,阅读原始 Spectre 和 Meltdown 论文(Kocher 等,2018;Lipp 等,2018)。这些论文正是为具备 CSAPP 所提供背景的实践者写的,并且具体展示了微架构优化如何变成安全漏洞。 |
| 把体系结构当成已经解决且静态的领域 | 2010 年左右的标准体系结构假设——单线程性能会持续提升,工作负载是通用的,x86 ISA 实际上具有普遍性——都已经发生变化。专用 accelerators、RISC-V 开放 ISA、推测执行的安全含义,以及持久内存的出现,正在重塑这个领域。教材知识代表的是印刷时为真的东西,而不一定是现在为真的东西。 | 通过处理器行业媒体(Chips and Cheese、Real World Technologies)以及 ISCA、HPCA、MICRO 和 ASPLOS 的论文跟进当代体系结构。Hennessy 和 Patterson 的图灵奖演讲 “A New Golden Age for Computer Architecture”(2017,免费)提供了为什么这个领域正在主动变化的最佳短篇说明。 |