DEV Community

Cover image for The 64-Byte Truck: Cache Lines Explained
Ajinkya Singh
Ajinkya Singh

Posted on Edited on AI-assisted

The 64-Byte Truck: Cache Lines Explained

Sources this series builds on: Chili's (ChiliTomatoNoodle) multithreading
playlist
,
CMU 15-213 (Computer Systems: A Programmer's Perspective), various CppCon talks,
and LLMs for restructuring and sanity-checking. Everything here is my own code,
compiled and run — not just transcribed.


Recap

Part 4 established why caching happens at all — spatial locality: if you
access one address, you're likely to want its neighbors soon, so hardware
fetches more than exactly what you asked for. This article answers the
obvious follow-up: how much more, exactly, and why that specific amount?

The answer is a fixed-size chunk called a cache line, and on most modern
CPUs that size is 64 bytes — always, no matter what you actually asked
for. This one fact is what the rest of this hardware track, and eventually
false sharing, is built entirely on top of.


What actually happens when you read one int

Say your code does int x = data[3]; and that triggers a cache miss — the
data isn't in cache yet, so it has to come from RAM. The natural mental
picture is the hardware reaching into RAM and pulling out exactly the 4
bytes that make up that one int. That is not what happens.

Instead:

int fetch

The entire 64-byte block gets pulled in and cached as one unit, and your
int gets read out of that block afterward. You paid for 64 bytes whether
you needed them or not.


Why 64 bytes specifically — two independent reasons

Reason 1: the physical width of the connection

The hardware path between RAM and the CPU (and between cache levels) is
physically built to move a fixed number of bytes per transfer — its "width."
On most modern CPUs that width is 64 bytes. The critical part:
transferring 4 bytes over this path takes the same amount of time as
transferring all 64
, because the limiting factor isn't the amount of data,
it's the fixed overhead of doing a transfer at all. Think of it like a
delivery truck that costs the same to dispatch whether it's carrying one box
or a full load — you fill the truck because the trip itself is the expensive
part, not the cargo.

Reason 2: spatial locality says it's worth it

Since nearby addresses are likely to be accessed soon anyway (Part 4), fetching
the surrounding 60 bytes "for free" — same transfer cost as fetching just 4 —
is a good bet almost all the time. Iterate over an array, and this pays off
constantly: fetching element 0 also fetches elements 1 through 15 (for 4-byte
ints), so the next several loop iterations are already cache hits before you
even ask.

why 64 byte


Cache lines are aligned, not centered on what you asked for

A subtlety worth being precise about: the 64-byte chunk fetched isn't "the 64
bytes starting at the address you asked for." It's the 64-byte block that
address falls inside, where blocks are laid out in fixed segments starting
from address 0 (0-63, 64-127, 128-191, and so on). That's what aligned
means here.

So asking for the int at address 1004 doesn't fetch bytes 1004-1067 — it
fetches whichever fixed 64-byte segment contains address 1004 (addresses
960-1023, in this example). The requested bytes might sit anywhere inside
that segment — start, middle, or end.

aligned cache

This is the single most important detail in this article. Because lines
are fixed, aligned segments — not "64 bytes around whatever you asked for" —
any two variables that happen to land inside the same aligned 64-byte segment
get fetched and cached together as one unit, entirely regardless of whether
your program treats them as related at all. The hardware has no concept of
"these are two different variables." It only knows about lines.


How much fits in one line

64 bytes holds, for example:

  • 16 ints (4 bytes each)
  • 8 doubles (8 bytes each)
  • A handful of small struct fields, packed together

So in practice, several "neighboring" variables in a real program — array
elements, adjacent struct fields, even separate global or static variables
that happen to land near each other by the compiler's own layout choices —
very commonly share a single cache line without anyone ever writing code
that groups them together on purpose.

storage


Glossary added this part

Term Meaning
Cache line / cache block The fixed-size chunk (typically 64 bytes) transferred and cached as one unit, every time
Bus width The fixed amount of data a hardware connection moves per transfer, regardless of how much was actually requested
Aligned Cache lines sit at fixed positions from address 0 (0-63, 64-127, ...), not centered on whatever address you asked for

Takeaways

  • A cache miss never fetches just what you asked for — it fetches the entire aligned 64-byte segment containing that address, every time.
  • 64 bytes is fixed by hardware bus width (fetching 4 bytes costs the same as fetching 64, so hardware always fetches the full amount) and justified by spatial locality (the extra bytes are usually useful anyway).
  • Cache lines are aligned to fixed 64-byte boundaries starting from address 0 — not centered on your specific address — which is why unrelated variables can end up sharing a line purely by accident of memory layout.
  • The hardware has no concept of "your variables." It only tracks lines. Two completely unrelated ints can be neighbors in the same 64-byte block with neither programmer ever knowing.

Top comments (0)