Lesson 08 / 16
Registers and Main Memory
The model of memory as an addressed array, the role of registers, and the access-cost gap between them.
Contents
The previous topic established how data is represented as bit patterns. These bit patterns have to reside somewhere, and where they reside directly determines program performance. This topic examines the structure of that place and how data is carried to the processor.
This lesson asks: how does memory appear to a program, and where does the processor keep the data it operates on?
Memory: An Addressed Array
The memory model a program sees is plain: an array of cells, each holding one byte, numbered starting from zero. A cell’s number is called its address, its contents its value.
| Address | 1000 | 1001 | 1002 | 1003 | 1004 |
|---|---|---|---|---|---|
| Value | 41 |
42 |
43 |
44 |
00 |
This model has two consequences. First, accessing memory requires two pieces of
information: which address, and how many bytes. The value 0x41424344 from the
previous topic is four bytes; reading it means requesting four bytes starting at
address 1000. How many bytes are interpreted, and in what order, is the rule defined
in the byte order lesson.
Second, an address is itself a number and can be stored in memory. A variable that holds another variable’s address — a pointer — follows directly from this observation. Address width determines the largest amount of memory that can be addressed: 32-bit addresses can address distinct bytes, roughly four billion cells; the limit for 64-bit addresses is a size no hardware reaches in practice.
Registers
The processor does not keep the values it operates on in main memory; the arithmetic and logic unit can only read from small, fast storage units built into the processor itself, called registers.
Registers have three distinguishing traits:
- They are few. A processor’s general-purpose registers number in the tens; main memory’s cells number in the billions.
- They have no addresses. Registers are referenced directly by name in an instruction, not by number.
- Their access is nearly free. Reading a register takes on the order of one processor clock cycle.
Some registers serve special roles: the program counter holds the address of the next instruction, the stack pointer holds the top of the call stack. Their function is defined in later lessons.
An addition is therefore not a single step. Computing a + b first loads the two
values from memory into registers, performs the addition between registers, and writes
the result back to memory. This load–operate–store pattern is the common skeleton of
processor architectures.
The Access-Cost Gap
The speed gap between registers and main memory is large enough to shape program design. Exact durations vary by hardware; what does not vary is the order-of-magnitude gap:
| Layer | Access order (clock cycles) |
|---|---|
| Register | ~1 |
| First-level cache | a few |
| Last-level cache | tens |
| Main memory | hundreds |
| Persistent storage | tens of thousands and up |
Each row is noticeably slower and noticeably larger than the one before it. This layering is called the memory hierarchy, and its design rationale is economic: fast storage is expensive, cheap storage is slow. The hierarchy strikes a balance between the two.
The consequence is this: a program’s speed depends as much on where it accesses data from as on how many operations it performs. A single value read from main memory can take as long as hundreds of arithmetic operations. This observation is the reason the cache, covered in the next lesson, exists at all.
How Storage Media Differ
The layers of the hierarchy differ not only in speed and capacity but in physical mode of operation.
Registers and cache are built from circuits that hold state as long as supply voltage is present; access is fast, cost per unit high. Main memory instead holds state in tiny electrical charges that leak over time, so it must be refreshed regularly. Refreshing is why main memory is both slower and cheaper: it uses fewer circuit elements per cell.
These three layers share one property: volatility. Their contents are lost when power is cut. Persistent storage, by contrast, retains its contents without power, but its access is orders of magnitude slower and writes are typically performed at the block level.
This volatility distinction explains why data a running program holds must be saved separately: state in memory is bounded by the lifetime of the process.
Word Width and Alignment
A word is the data width a processor natively operates on; it coincides with the width of its general-purpose registers. On a 64-bit processor, a word is eight bytes.
Hardware performs multi-byte accesses relative to word boundaries. Because of this, the
placement of data structures in memory follows the alignment rule: the address of
an -byte value is generally chosen to be a multiple of . A four-byte integer is
placed at address 1000, not at 1001.
The cost of an unaligned access varies by hardware: some processors slow it down by splitting it into two memory operations, others raise a fault.
The visible consequence of the alignment rule is padding. When small and large fields are declared consecutively in a structure, the compiler inserts unused bytes between them:
struct A struct B 1-byte field 8-byte field 7 bytes padding 1-byte field 8-byte field 7 bytes padding total: 16 bytes total: 16 bytes
Declaring the same fields in a different order can change the structure’s size in memory. Ordering fields from largest to smallest width is a common habit that minimizes padding. This detail also explains why the previous lesson noted that a raw memory copy is not portable: the amount of padding depends on the compiler and the hardware.
Seeing Memory Layout
The following program examines different representations of the same data and the space an object occupies in memory:
import sys, array value = 0x41424344 # Four bytes to be written to memory (in big-endian order) raw = value.to_bytes(4, "big") for offset, byte in enumerate(raw): print(f"address+{offset}: {byte:#04x}") # 0x41 0x42 0x43 0x44 # Fixed-width arrays keep elements contiguous and aligned. arr = array.array("I", [1, 2, 3, 4]) # 'I': 4-byte unsigned integer print(arr.itemsize, len(arr)) # 4 4 print(arr.buffer_info()[1] * arr.itemsize) # 16 — total bytes # Python objects take up far more space than raw data. print(sys.getsizeof(1), sys.getsizeof(arr)) # sizes include object header
The array module stores elements at fixed width and contiguously; this is the closest
Python equivalent to a hardware-friendly data structure layout. A general-purpose list,
by contrast, holds pointers to its elements: the values themselves sit scattered in
memory. The difference between the two layouts becomes visible in the cache behavior
covered in the next lesson.
Summary
- To a program, memory is an array of addressed byte cells; access requires both an address and a width.
- An address is itself a number and can be stored in memory; the concept of a pointer follows from this observation.
- The processor operates only on registers; computation proceeds in a load–operate–store pattern.
- There is an order-of-magnitude speed gap among registers, cache, main memory, and persistent storage; this layering is the memory hierarchy.
- Multi-byte values are placed according to the alignment rule; the rule causes padding bytes and order-dependent size differences in structures.
Next Step
Where data resides has now been defined. Instructions themselves reside in memory too,
and share the same address space. The next lesson covers the cycle by which the
processor fetches an instruction from memory and executes it, and how that cycle
produces program flow; there, the pattern 0x41424344 will be read once more, this
time as an instruction.
To keep your progress and take notes, Log in
My notes
Log in to take notes.