SysadminNews

A Linux MM Patch Set Shows Up to 12.8x Gains in gup_test

On this page
  1. The function, and the arithmetic problem
  2. Who is actually paying this cost
  3. How to read 12.8x
  4. What happens next
  5. Sources and further reading

Rik van Riel has posted a request for comments patch series that teaches one of the Linux kernel's least glamorous functions to stop doing everything one page at a time. The function is follow_page_mask, which turns a virtual address into the struct page behind it, and today it walks the page tables for every single page. Van Riel's patches let a walker hand back more than one page per call. The kernel's own gup_test benchmark reports gains of up to 12.8x. If you pin memory for device assignment, RDMA or registered buffers, this is the code path underneath the wait you have learned to tolerate.

The short answer

Meta engineer Rik van Riel has posted a request for comments series that batches lookups in the Linux kernel's follow_page_mask, letting page table walkers return more than one page per call instead of resolving a single page at a time. The main beneficiary is get_user_pages, the path behind memory pinning for VFIO device assignment, RDMA and registered buffers. The kernel's gup_test benchmark reports improvements of up to 12.8x.

12.8xbest reported gup_test improvement
1page per call, in the code as it stands today
RFCstatus, so the design can still change
Answer card describing Rik van Riel's request for comments patch series that batches lookups in the Linux kernel follow_page_mask function so page table walkers can return more than one page per call, benefiting get_user_pages callers, with the kernel gup_test benchmark reporting improvements of up to 12.8 times.
What the patch series changes, and what it measured. Source: Phoronix, August 11, 2026. PNG

Some kernel optimisations are interesting because they are clever. This one is interesting because of how obvious it looks in hindsight, which is usually the sign that the hard part was somewhere other than the idea.

Rik van Riel, a Meta engineer with a long history in Linux memory management, has posted a request for comments series that changes how follow_page_mask behaves. Today it resolves one page per call. His patches let a page table walker hand back more than one. The kernel's gup_test benchmark shows improvements of up to 12.8x.

The function, and the arithmetic problem

follow_page_mask translates a virtual address into the struct page behind it. To do that it walks the page tables, descending through each level until it reaches the entry it wants.

You will almost never call it yourself. You depend on it constantly, because it sits under get_user_pages, the interface the kernel uses whenever it needs the actual physical pages behind a user space buffer rather than just a range of addresses.

The problem is not that a single walk is slow. It is that a walk resolves exactly one page, so pinning a large buffer means performing that walk once per page, and consecutive pages very often live in the same region of the same page table. Each walk descends through levels the previous walk just visited, arrives one entry further along, and returns. Then the next call starts from the top again.

Batching removes that repetition. If the walk has already reached a table containing a run of pages the caller wants, hand back the run.

Who is actually paying this cost

The callers that matter are the ones that pin user memory so something other than the CPU can reach it.

VFIO device assignment. Giving a virtual machine direct access to a GPU or a network card means pinning the guest's memory so the device can DMA into it safely. On a guest with a large memory allocation, that pinning is a real, measurable pause at startup.

RDMA. Memory regions are registered in advance so a remote host can write into them without involving the local CPU. Registration pins the pages, and the cost lands when a queue pair is created rather than during the transfers everyone benchmarks.

io_uring registered buffers, direct I/O, and assorted DMA paths, all for the same underlying reason.

The shared shape is worth naming: these are setup time costs, paid in large chunks, at moments users notice. A guest booting. A job scheduling. A connection establishing. Nobody profiles them because they are not in the steady state, and everybody waits for them.

Comparison card showing the current behaviour where each get_user_pages iteration performs a full page table walk to resolve a single page, next to the batched behaviour where one walk resolves a run of contiguous pages and returns them together to the caller.
One walk per page, against one walk per run of pages. Simplified illustration of the approach described in the patch series. PNG

How to read 12.8x

Carefully, and with respect for what the number is.

gup_test is a program in the kernel tree written to exercise get_user_pages and related memory management paths. It exists to make this code run hot so developers can measure it. That is exactly what makes the number useful in review and misleading in a headline.

A 12.8x improvement on gup_test means the optimised path got dramatically faster. It does not mean any real workload gets 12.8x faster, because no real workload spends all its time in get_user_pages. What you would actually see depends on how much of your wall clock was sitting in this function, which for most software is none, and for a host pinning tens of gigabytes for device assignment is a genuinely interesting fraction.

That is the correct way to hold benchmark numbers from RFC patches in general. The measurement tells you the ceiling, and your profile tells you the share.

What happens next

RFC means the author is asking whether the approach is right, not proposing a merge. Memory management review is unhurried by design, because a mistake here does not produce a bug in one subsystem, it produces corruption anywhere. Expect revisions.

There is also a broader pattern here worth noting. A lot of recent kernel work has been about removing per item overhead in paths that were designed when the items were fewer and smaller, whether that is Linux 7.3 letting applications claim the VRAM they were promised or cluster load balancing fixes for Intel hybrid CPUs. Machines got much larger. Code that iterates once per unit did not always follow.

If memory pinning cost currently shapes how you architect something, the useful takeaway is not a version number. It is that the constraint is under active work, and worth re measuring in a year.

Sources and further reading

Frequently asked questions

What is follow_page_mask and why should I care what it does?

It is the kernel function that translates a virtual address into the struct page that backs it, by walking the page tables level by level. Almost nothing calls it directly, which is why it is unfamiliar, but a great deal of the kernel depends on it indirectly through get_user_pages. Every time the kernel needs the physical pages behind a user space buffer, rather than just the addresses, this is roughly the path taken. The reason it matters now is arithmetic: it currently resolves one page per call, so a large buffer means a very large number of calls, each repeating a page table walk that mostly rediscovers what the previous one found.

What exactly do these patches change?

They let a page table walker return more than one page per call. Instead of asking for a page, getting a page, and starting the walk again from the top for the next one, a caller can be handed a run of pages that the walk already resolved. The work is a request for comments series, which in kernel terms means the author is asking for review of the approach rather than proposing a merge. Van Riel is a Meta engineer with a long record of memory management work, so the series carries weight, but RFC is a real label and the design can still change substantially before anything lands.

Where does the 12.8x figure come from, and how should I read it?

From gup_test, a program in the kernel tree written specifically to exercise get_user_pages and related memory management paths. Phoronix reported gains of up to 12.8x on that benchmark with the RFC patches applied. Read it as a measure of the function in isolation, not a prediction for your workload. gup_test is designed to hammer exactly the code being optimised, which is what makes it useful for the developer and misleading if quoted as a system number. The honest summary is that a hot inner loop got dramatically faster, and how much of that reaches you depends entirely on how much time you were spending inside it.

Which real workloads sit on top of get_user_pages?

The ones that need the kernel to pin user memory so something other than the CPU can reach it. Device assignment through VFIO, which is how a virtual machine gets direct access to a GPU or a network card. RDMA, where a remote host writes into memory registered in advance. io_uring registered buffers. Direct I/O paths. Various DMA arrangements. What these have in common is that they pin large regions at setup time, so the cost lands as a pause when a guest starts, when a queue pair is created, or when a buffer set is registered. Those are exactly the moments this work targets.

When would this reach a kernel I can run?

Not soon, and a specific version would be a guess. An RFC series is the beginning of the review conversation, not the end. Memory management patches are held to a high bar because the blast radius of a mistake is the entire system, so the realistic path is several revisions in response to review, then a merge window, then whatever your distribution's kernel policy allows. The reason to know about it now is planning rather than deployment. If pinning cost is currently shaping your architecture, it is useful to know that the constraint is being worked on rather than fixed forever.