Turning a memory address into a real one inside a virtual machine can take twenty-four memory accesses one after another, because the address table is a tree walked by following pointers and every step of the guest's four-step walk needs its own four-step host walk to resolve — and five-level tables push that to thirty-five; replacing the tree with a hash table collapses it to three, but this paper (Illinois and CMU) shows the obvious version of that gains almost nothing, because a hash lookup is not one access when you must hedge across page sizes and collision slots, and nesting SQUARES the hedge from nine parallel accesses to eighty-one.
The argument in one glance
-
Problem
Twenty-four memory accesses, one after another, to resolve one address
Every memory access a program makes needs its virtual address turned into a physical one, and the table that does this is organised as a four-level tree walked by following pointers — four sequential memory accesses in the worst case. Inside a virtual machine it is far worse: the addresses in the guest's own table are themselves virtual from the host's point of view, so resolving EACH of the guest's four steps requires a complete four-step host walk of its own. Counted end to end, a single translation can take twenty-four sequential memory accesses, and processors adopting five-level tables push that to thirty-five. For some applications this reaches more than half of total execution time.
-
What people did
Replace the tree with a hash table
A tree is walked sequentially because each level tells you where the next one is. A hash table has no such chain: hash the address, read the entry, done. Applied to both guest and host, this collapses the twenty-four steps to three — read the host table to find where the guest table entry physically lives, read that guest entry to get the guest physical address, read the host table again to translate it. Hashed page tables have shipped in real architectures before, and recent work solved their long-standing problems with collisions, multiple page sizes and resizing.
-
Why it fell short
A hash lookup is one access only if you know where to look
You do not know the page size in advance, so there is a separate table per size and all must be checked. Collisions are resolved by allowing an entry to live in any of several slots, so all of those must be checked too. A lookup is therefore not one memory access but a fan-out of them — nine, for three page sizes and three slots. Nesting SQUARES this: every one of the nine guest addresses must itself be translated, each fanning out to nine host accesses, giving eighty-one parallel accesses for the first step alone. The paper measures the naive combination and finds it worth about 5 percent, because the parallel traffic overwhelms the cache bandwidth it needs.
-
This paper's idea
Keep the three-step latency, and prune the fan-out that pays for it
This paper (Illinois and CMU — Stojkovic, Skarlatos, Kokolis, Xu, Torrellas) treats the fan-out as the real problem. Small tables record which table and which slot actually holds each translation, so the hardware can narrow eighty-one speculative accesses down to one; these exist for both guest and host. A new cache fixes an awkward recursion, where the structure that prunes guest accesses itself needs translating to be read. Another is enabled and disabled at runtime according to whether the application shows the locality to benefit. The measured effect is decisive: the plain design gains 5 percent, and these techniques take it to 19 percent for ordinary pages and 24 percent with huge pages.
Why this problem is worth solving
The cost is structural rather than incidental, and the arithmetic is worth doing
slowly.
A
PAGE TABLE turns the virtual addresses a program uses into the physical addresses
memory actually has. It is organised as a tree — four levels on current
processors — walked by reading a level, which tells you where the next level is,
and so on: four sequential memory accesses. Inside a virtual machine there are
two such tables. The guest operating system maps its virtual addresses to what it
believes are physical ones, and the hypervisor maps those to real physical
addresses. The catch is that the guest’s table lives in memory the guest also
addresses virtually, so to read ANY level of the guest table the hardware must
first run a complete four-step host walk to find where that level physically
sits. Four guest steps each needing a four-step host walk, plus a final four-step
walk to translate the answer, comes to TWENTY-FOUR sequential memory accesses.
Five-level tables, already appearing in processors, take it to thirty-five
The alternative removes the chain, but introduces a different cost that this paper
is really about.
Hashing an address gives you
the location of its entry directly, with no pointer chain, so a nested
translation needs only three accesses instead of twenty-four. But that assumes
you know which table and which slot. Two things break it. First, systems support
several PAGE SIZES — typically 4KB, 2MB and 1GB — and a translation lives in the
table for its own size, which you do not know beforehand, so all three tables
must be consulted. Second, hash collisions are resolved by CUCKOO HASHING, where
an entry may legitimately live in any of several slots, so all of those must be
read as well. With three page sizes and three slots that is nine parallel
accesses for one lookup. In a nested setting the two fan-outs MULTIPLY: each of
the nine guest addresses is itself virtual and needs its own nine-way host
lookup, giving eighty-one. Parallelism has converted sequential latency into
parallel bandwidth, and nesting squares the exchange rate
What people did before, and where it broke
- Cache the translations. Multi-level translation buffers hold recent results, page-walk caches hold intermediate tree levels, and nested buffers cache the four-step host walk needed to reach each guest level. These are real and effective, and every shipping processor has them.
- But they are no longer sufficient. Translation still reaches more than half of execution time for some applications, buffer access times have overtaken second-level cache access times, and memory capacities heading into terabytes make the working sets worse.
- Use huge pages. Mapping 2MB or 1GB at a time shrinks the metadata and cuts levels off both walks, which genuinely helps and is measured here as a large effect.
- But it shortens the chain rather than removing it. The walk is still sequential, and applications that cannot use huge pages for their whole dataset get partial relief at best.
- Combine nested and shadow paging. Agile Paging exploits the observation that upper levels of the tree rarely change, switching between mechanisms to avoid redundant work.
- But it still leaves four sequential accesses in the best case, plus hypervisor intervention. Simulated in an idealised form with no hypervisor cost at all, it is still 16 percent behind the design here.
- Put a very large translation buffer in memory itself. This removes a substantial fraction of page walks outright.
- But a miss in it still costs a full walk, and the access to it is itself a memory access.
- The gap. Everyone was shortening or avoiding the sequential chain. Hashing eliminates it, and the prior work that made hashing practical for native systems existed — but nobody had checked what happens when you nest it, and the answer is that the parallel fan-out squares and eats the gain.
How the fan-out gets pruned back down
Collapse twenty-four steps to three, then attack the parallel traffic that makes three expensive
-
Walk two trees, one nested inside the other
Read a guest table level, but first run a complete four-step host walk to find where that level physically lives. Repeat for all four guest levels, then translate the final answer. Twenty-four sequential memory accesses, each dependent on the last.
-
Hash instead of walk, for both guest and host new
Give guest and host hash tables rather than trees. A nested translation becomes three accesses: the host table to find where the guest entry physically sits, the guest entry to get a guest physical address, and the host table again to translate it. The sequential chain is gone.
-
Accept that each of those three is a fan-out new
One table per page size, and cuckoo hashing puts an entry in any of several slots, so a lookup speculatively reads all of them. Nine parallel accesses natively; nested, the guest fan-out multiplies by the host fan-out, giving eighty-one for the first step.
-
Record where each translation actually lives new
Maintain small side tables saying which page-size table and which slot holds a given translation, and cache them in the translation unit. On a hit, the eighty-one speculative accesses collapse toward one. Provide these for both guest and host, managed by the guest system and the hypervisor respectively.
-
Break the recursion in the guest side table new
When the guest side table misses, reading it requires translating its own address first — a translation to fix a translation. A small dedicated cache holds those particular mappings, so the fix does not cost what it saves. Ten entries reach nearly perfect hit rates.
-
Turn the last cache on or off based on measured locality new
Caching the finest-grained host side table helps when the application has page locality and thrashes the cache when it does not. Start it enabled, watch its hit rate, disable it if poor, and re-enable if the next coarser level starts hitting well. Applications settle quickly into one state.
The idea
Parallelism does not remove work, it moves it from latency to bandwidth. The appeal of hashing is easy to state — twenty-four dependent accesses become three — and it is genuinely what happens to the critical path. What the naive version misses is what those three accesses cost. Because the page size is unknown and collisions put an entry in any of several slots, each lookup speculatively issues nine memory accesses and discards eight. In a native system that is a tolerable trade. Nested, each of those nine addresses is itself virtual and needs its own nine-way lookup, so the first step alone issues eighty-one. The paper’s most useful measurement is of the design it does not propose: taking a hashing scheme that works well natively and nesting it directly yields about 5 percent, because the traffic saturates the cache bandwidth the design depends on. Even the final system issues 13 to 15 percent more memory requests than the tree it replaces — it wins because those requests overlap. Recognising that a technique which looks free on the critical path is actually spending a different resource, and that the exchange rate can be brutal in a new setting, is the same check-what-you-are-actually-paying-with reasoning covered elsewhere on this site where a design accepts more of one quantity to buy less of another and must measure both.
Almost all the gain comes from the unglamorous part. The headline idea is the hash table; the contribution is the pruning. Small side structures record which page-size table and which slot really holds each translation, so the hardware can narrow the speculative fan-out — ideally to a single access — before issuing anything. This paper provides them for guest and host separately, managed by the guest operating system and the hypervisor respectively, and the measured split is stark: the plain nested hashing design is worth 5 percent, and the pruning techniques carry it to 19. One of them addresses a recursion that only exists because of nesting: when the structure that prunes GUEST accesses misses, reading it requires translating its own address, so a small dedicated cache is added for exactly those mappings. Ten entries suffice. Reporting that the novel-sounding mechanism contributes a quarter of the result while three supporting caches contribute the rest is the kind of honest decomposition covered elsewhere on this site where a paper’s own ablation shows which of its parts is actually load bearing.
Flexibility has a price, and here it closes off the obvious next step. The scheme uses cuckoo hashing so that collisions never require a chain, and resizes its tables while the process runs so they track its memory use. Both are why it works. Both also mean an entry’s physical location is not stable: inserting one translation can shuffle others between slots, and a resize migrates everything. That has a specific consequence the authors identify and accept. The three remaining accesses could plausibly become two, by caching the pointer found in the first step that says where the guest entry lives — the natural analogue of what existing processors already cache for trees. But those pointers go stale every time a rehash or resize moves the entry they point at, and keeping them correct would mean flushing. So the design declines the optimisation and stays at three. Identifying that the mechanism giving a design its flexibility is precisely what forbids a further improvement, and saying so rather than working around it, is the same name-the-cost-of-your-own-choice discipline covered elsewhere on this site where an approach’s enabling property turns out to also be its ceiling.
One address, three accesses, eighty-one hedges
Following a nested translation, and where the parallel traffic comes from
Three page sizes and three cuckoo slots throughout, which is the configuration evaluated.
Step 1 of 5
What the tree version costs
The guest table has four levels, and reading any one of them means first discovering where it physically lives, which is a complete four-level host walk. Four guest levels each preceded by a four-step host walk, plus a final host walk to translate the result, is twenty-four memory accesses in sequence — each one waiting on the last.
the buffers that cache these results are effective but insufficient, with translation exceeding half of execution time on some applications processors adding a fifth level take the worst case from twenty-four to thirty-five
Why it works
- Hashing removes the pointer chain entirely → three accesses replace twenty-four regardless of how many tree levels there would have been → so the design also absorbs the move to five-level tables that would take trees to thirty-five.
- Page size and collision slot are both unknown, and nesting requires translating every speculative address → the fan-out squares from nine to eighty-one → which is why nesting a natively-good design directly yields only about 5 percent.
- Side tables record where each translation really lives → the hardware narrows the fan-out before issuing anything → bringing measured averages to 2.8, 2.8 and 1.6 accesses per step and carrying the gain from 5 percent to 19.
- The finest-grained host structure helps some applications and thrashes for others → enabling it, watching its hit rate, and disabling it when poor lets each application settle → contributing about 4 percent that a fixed policy could not get for both groups.
- Sequential chains produce long latency tails while parallel bursts do not → the distribution changes shape, not just its mean → so walks that ran to several hundred cycles typically complete in about four memory-access times.
What it costs (trade-offs and scope)
- It uses more memory bandwidth, not less. The design issues 13 to 15 percent more requests to the cache hierarchy than the tree it replaces. It wins because they overlap, which means the benefit depends on there being bandwidth to spend.
- The headline mechanism is a small part of the result. Plain nested hashing gives 5 percent; the three supporting caches give the rest. One of the four contributions, exploiting knowledge that page tables use 4KB pages, is measured at 0.4 to 0.5 percent and the authors say so.
- Three accesses is a floor, not a target. Caching the pointer found in the first step would make it two, but rehashing and resizing move entries and would make those pointers stale, so the optimisation is declined.
- It needs new hardware and coordinated software. New caches in the memory management unit, plus side tables maintained separately by the guest operating system and the hypervisor. The migration path helps but does not remove this.
- Everything is simulated. Results come from full-system simulation of 500 million instructions per application after warm-up, not from hardware, though some huge-page behaviour was checked against real systems.
The evaluation
Eleven applications were simulated with footprints from 6.9 to 64 gigabytes — eight graph analytics kernels, a bioinformatics sequence matcher, a random-access HPC benchmark and a database workload — each run for 500 million instructions after a 50 million instruction warm-up, with a deterministic methodology that needs no error bars. Configurations covered nested and native, with and without huge pages, plus a hybrid in which only the host side is converted, and an idealised version of a competing technique given no hypervisor cost at all.
Translating an address inside a virtual machine
| Property | Tree page tables | Hashing, nested directly | Hashing with fan-out pruning |
|---|---|---|---|
| Accesses on the critical path | |||
| Parallel accesses per step | |||
| Memory requests issued | |||
| Shape of the latency distribution | |||
| Speedup over trees |
Check yourself
Check your understanding
1. A native page walk takes up to four sequential memory accesses. Predict why the nested version takes twenty-four rather than eight.
2. Taking a hashed page table design that works well on a native system and nesting it directly yields only about 5 percent. Predict what goes wrong.
3. The structures that prune the fan-out are cached, and a miss in the guest-side one is handled differently from a miss in the host-side one. Predict why.
4. The design could cut its three remaining accesses to two by caching a pointer found in the first step, which is what processors already do for trees. It declines. Predict why.
Sources
- Parallel Virtualized Memory Translation with Nested Elastic Cuckoo Page Tables (author PDF, Carnegie Mellon) — ASPLOS 2022; the full 14-page paper, read in full for this explainer.
- ACM Digital Library (DOI 10.1145/3503222.3507720) — published version.