Answers · How a program runs
How does the CPU avoid walking the page table on every single memory access?
In shortA small cache of recent translations, called the TLB, is checked on every memory access; a hit skips the page-table walk, and a miss walks once then stores the result.
A small cache of recent virtual-to-physical translations, usually called the TLB, sits in front of the page table and is checked on every memory access. A hit supplies the physical address immediately, so the page table is not walked at all; only a miss triggers a walk, and the result of that walk is stored for next time.
The TLB is not a replacement for the page table. It is a record of answers already found: virtual page number to physical frame number, plus permission bits. Hardware looks there first. On a hit the physical address is formed and the data cache lookup proceeds; the page table is never consulted. On a miss hardware either walks the table in hardware or traps to software, depending on the architecture, and then writes the translation into the TLB. The next access to the same page, or to another page in the same large block, hits.
The bill for a walk
The page table is itself ordinary memory, so an uncached translation is not a free indirection. On a common 64-bit machine the map is four levels deep. If each level must be read from main memory at about 80 ns, a walk costs four reads before the data read can begin. Then the data read itself costs 80 ns, so the total is 320 + 80 = 400 ns. That is 400 / 80 = 5 times the cost of a bare memory read, which would make every load run at one fifth of the speed of a machine with no translation at all.
Nothing about that arithmetic is wrong. It is simply the price of the rare case. The TLB is consulted on every access, including instruction fetches, which forces it to be tiny: it must answer in a fraction of a cycle. Typical fastest levels hold on the order of 64 entries, and a second, slightly slower level holds 1,536 entries. A data cache can hold tens of megabytes. The translation cache is therefore one of the smallest structures in the machine, and its smallness is exactly why its coverage is limited.
- the amount of memory whose translations can be held at once
- the number of entries in the translation cache
- the block size, four kilobytes by default
Put real entry counts into . With 64 entries and 4 KB blocks, . With 1,536 entries and the same 4 KB blocks, . That is the whole coverage of the second-level translation cache on a default configuration. A service touching hundreds of megabytes is far past it. The same 1,536 entries with 2 MB blocks give . A configuration with 32 entries and 1 GB blocks gives . The entry count did not move; only the size of the block each entry describes did.
What the free-looking picture misses
The natural guess is that the CPU checks the page table the way it checks a register, as a fixed step that can be ignored. It cannot. Page table entries live in memory, and the lower levels of the table are themselves addressed through the upper levels, so a cold walk is a chain of dependent memory reads. A TLB miss is not a page fault either: the page can be present and valid, and the only missing thing is the cached translation. The shortcut is not an optimisation bolted onto a working scheme; without it the scheme would cost several memory reads per access, and the machine would be too slow to use this way.
The step, not the slope
Coverage produces a cliff rather than a gentle decline. While the working set fits inside , entries stay resident across passes and translation is effectively free. Once the working set passes , entries are evicted before they are reused, each pass re-walks, and time per element jumps. The cost then flattens again: growing from just past the cliff to far past it does not make each miss much more expensive, because the miss was already the expensive event. This is why two benchmarks, one at 2 MB and one at 8 MB, can both look reasonable while the interesting change happened between them.
Larger blocks are the only response that needs no source change. A block is allocated whole whether it is used or not, so large blocks waste memory, and some systems require them to be reserved at boot. For a database buffer pool or one enormous allocation they are close to free money. They do not rescue a program that jumps randomly around a working set larger than the covered amount. Sequential access does better because one translation serves a whole block and thousands of following accesses are free; touching less memory is the real fix when the working set cannot fit.
Worth keeping
- A TLB hit costs no page-table memory reads; a miss costs up to four on a four-level 64-bit page table.
- A translation cache covers entries times block size, so 1,536 entries with 4 KB blocks cover 6 MB, while the same entries with 2 MB blocks cover 3 GB.
- Performance is flat while the working set fits the covered memory and steps down once it does not, so benchmarks just inside and just outside the cliff can look wildly different.
Where this goes next
This question is one step of Memory, All the Way Down, a course that works through its subject from the beginning. The page that teaches this part of it is What Translation Costs, and it reads the whole way through whether or not you have an account.
What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.
Read that pageThe whole courseOther questions people ask about this
- Why does reading one byte from memory load 64 bytes?Memory hardware moves data in fixed sixty-four-byte blocks, so a one-byte read fetches the whole block; the extra bytes cost almost nothing because reaching the memory row costs most of the time.
- Why does every program I run see the same memory addresses?A pointer's number is a private label for a block of memory, not a physical chip location, so two programs can use the same label for different places.
- Why does 2 + 3 * 4 equal 14 and not 20?The multiplication is done first: three times four makes twelve, and only then is the two added, giving fourteen. Reading left to right would give twenty.
- How many samples does a profiler need before a percentage in the report means anything?It depends on how many samples landed on that line: you need a few hundred samples on the line you care about, because the relative error is roughly one over the square root of that count.
- If I make one part of my program twice as fast, how much faster does the whole program get?Less than twice as fast: only the share of run time you actually touch can improve, so the untouched remainder caps the gain, often at a few per cent.
- Why can two threads that each work correctly still give the wrong answer?Each thread reads the shared value and writes it back in two separate steps, so the other thread can run in between and one increment silently overwrites the other.