skip to content

CPU caches do not move memory one byte at a time; they transfer and track it in fixed-size aligned blocks called cache lines (commonly 64 bytes on mainstream hardware). What is a cache line, and what consequences does that granularity have for how a program performs?

level: juniorimportance: should knowfreq 45%

answer

  1. Unit of transfer, tag and coherence
  2. ~64 bytes, hardware property not language
  3. Touch one byte, move the whole line
  4. Sequential scan amortises misses; pointer-chase does not
  5. Sharing is per line → false sharing

basics

~20 s

A cache line is the fixed-size block of memory (often 64 bytes) a cache loads, owns and invalidates as one unit. So neighbouring data travels together: sequential access is nearly free, scattered access wastes most of each transfer, and unrelated variables sharing a line interfere.

solid answer

~50 s

A cache line is the unit of transfer and of bookkeeping between memory and a CPU cache: typically 64 bytes, though that is a hardware property, not a language guarantee. Touching one byte pulls in the whole line, and the cache tracks state (valid, dirty, shared with other cores) per line rather than per variable. Three consequences follow. **Spatial locality pays**: a contiguous scan amortises one fetch over many elements, so it can be an order of magnitude faster than pointer-chasing the same number of items. **Scattered access wastes bandwidth**: reading one 4-byte field per object still moves a full line per object, so layout (fields together vs. columns of arrays) changes throughput without changing algorithmic complexity. **Sharing happens per line, not per variable**: two threads writing two logically unrelated variables that land in one line contend in hardware. That last effect is false sharing, which is why hot per-thread data is often padded apart.

code

text · 7 lines
text
array of records (16 fields x 4 bytes each):
  [rec0: f0 f1 ... f15][rec1: f0 f1 ... f15] ...
  reading only f0 -> 4 useful bytes per 64-byte line  (6%)

column arrays (one array per field):
  f0: [rec0 rec1 rec2 ... rec15][rec16 ...]
  reading only f0 -> 64 useful bytes per line       (100%)

go deeper

for a junior

Be able to define the line as the block caches move and track, quote the typical 64 bytes, and say why sequential access is faster than random access.

for a middle

Add the mechanics: per-line tags and state, prefetching on predictable strides, and the fact that coherence is tracked per line, which sets up false sharing.

for a senior

Talk about layout as a tuning lever — hot/cold splitting, columnar layouts, alignment — and how you would measure cache misses rather than guess.

for a principal

Frame it as a data-architecture question: what the working set is, how much bandwidth the design implies per operation, and when layout changes beat algorithmic changes.

## What a cache line is A CPU cache is a small fast memory holding recently used pieces of main memory. It does not store variables; it stores fixed-size, naturally aligned blocks of physical memory called **cache lines**. On most current mainstream CPUs a line is 64 bytes; some architectures use 32 or 128, and some prefetch lines in adjacent pairs. Nothing in a programming language promises a size, so portable code treats it as a tunable constant, not a fact. The line is the unit of three different things at once: - **Transfer** — a miss fetches the whole line from the next level or from DRAM. - **Tagging** — the cache stores one tag and one state per line, not per byte. - **Coherence** — when multiple cores cache the same memory, the hardware protocol negotiates ownership at line granularity. ## Why blocks instead of bytes Two reasons. First, **spatial locality**: programs that touch address X usually touch X+1 soon after, so fetching neighbours speculatively is usually free work that pays off. Second, **economics**: DRAM and interconnects are efficient in bursts, and per-byte tags would cost more storage than the data. Larger lines amortise both, at the cost of moving bytes you may never read. ## Consequence 1: locality dominates access cost Rough orders of magnitude on a modern core: an L1 hit is a few cycles, L2 around a dozen, L3 several dozen, DRAM a couple of hundred cycles. A sequential array walk hits memory once per line (16 four-byte elements), and hardware prefetchers recognise the stride and fetch ahead, so most accesses hit L1. A linked structure scattered across the heap pays a potential miss per node and defeats the prefetcher because the next address is only known after the current load returns. Same asymptotic complexity, very different wall-clock time. ## Consequence 2: layout is a performance decision If you iterate over a million records reading a single small field, an array-of-records layout drags every other field through the cache with it; a columnar layout (one array per field) packs only the field you need, so each line delivers 16 useful values instead of one. Similarly, splitting an object into a hot part (fields touched every iteration) and a cold part can multiply effective cache capacity. None of this changes what the program computes — only how many lines it must move. ## Consequence 3: sharing is per line Because coherence state is per line, two threads that write two distinct variables located in the same line are treated by hardware as if they were writing the same thing: the line bounces between the cores' caches. This is **false sharing** — no logical sharing, real hardware contention. The mirror image is **true sharing**, where the variable really is shared. Both are line-level effects, and both are invisible in the source code, which is exactly why they surprise people. ## Consequence 4: alignment and boundaries A value that straddles a line boundary needs two lines and, on some hardware, extra work for atomicity. Allocators typically align larger blocks to line boundaries, but small adjacent allocations frequently share one. That is why deliberate fixes (padding a structure to a full line, or aligning an allocation) are stated in terms of the line size. ## What to say in an interview Define the line as the unit of transfer, tagging and coherence; give the typical 64-byte figure while noting it is hardware-specific; then give one performance consequence for a single thread (locality and prefetching) and one for many threads (sharing is per line, hence false sharing). Finish by saying the practical implications are about **data layout**, which is usually a cheaper lever than changing algorithms.

  • How can two variables that no line of code ever shares still end up in the same cache line?
    Because placement is decided by the allocator and the object layout, not by the program's notion of ownership. Two fields declared next to each other, or two small objects allocated back to back, commonly land within the same 64-byte block. The hardware then treats them as one unit for coherence purposes, so writes by different threads collide.
  • Why is walking a linked list usually far slower than scanning an array with the same number of elements?
    An array scan touches one new line every 16 or so elements and the prefetcher fetches lines ahead of the loop, so most accesses hit L1. A linked list may put every node on a different line, and the address of the next node is only known once the current load completes, which serialises misses and defeats prefetching. The complexity is identical; the memory behaviour is not.

Memory moves like shipping pallets, not individual cans. Ordering one can still ships the pallet, and if two departments keep items on the same pallet, every time one department edits its item the whole pallet has to be shuttled back and forth.

saying these in an interview costs you the question

  • Saying a cache line is the same thing as a memory page (pages are kilobytes and are a virtual-memory concept)
  • Claiming 64 bytes is guaranteed by the language or by all CPUs
  • Believing that reading one field loads only that field
  • Assuming cache effects only matter for very large data sets, when layout affects even small hot loops

context