Skip to content

mm/gup: batch contiguous pages in follow_page_mask()

Rik van Riel linux-mm lkml 1 version In review

Get series with b4
b4 am [email protected]

Versions newest first

  1. v1 Latest5 patches 1 replyIn review

Patches in v1 No applies recorded yet

#Subject and filesSizeRepliesApplied
1/5mm/gup: convert follow_page_mask() to return a longmm/gup.c+148 -1210 replies
2/5mm/gup: split follow_page_pte_commit() out of follow_page_pte()mm/gup.c+53 -330 replies
3/5mm/gup: add gup_fill_pages() and use itmm/gup.c+20 -150 replies
4/5mm/gup: return a huge page's full count from follow_page_mask()mm/gup.c+78 -990 replies
5/5mm/gup: walk multiple PTEs per follow_page_pte() callmm/gup.c+145 -640 replies

Reviews trailers per patch

No Reviewed-by, Acked-by or Tested-by trailers for v1 yet.

Read the v1 thread

Cover letter [PATCH 0/5] mm/gup: batch contiguous pages in follow_page_mask()

follow_page_mask() walks the page tables one page at a time, even when the caller asked for a whole run of contiguous pages. Every page of a large folio re-enters the pmd/pud/pte walk and re-takes the page table lock.

This series changes follow_page_mask() to return a page count and a new @end argument bounding how many pages remain, instead of a single struct page, so a walker can hand back more than one page per call.

Patch 1 converts the follow_page_mask()/follow_p4d_mask()/ follow_pud_mask()/follow_pmd_mask()/follow_page_pte() call chain to return a long instead of a struct page pointer or ERR_PTR(), with no functional change: every path still handles exactly one page.

Patch 2 is pure code motion, splitting the "commit to a resolved page" tail of follow_page_pte() into its own follow_page_pte_commit(), no functional change.

Patch 3 adds gup_fill_pages(), a small helper that fills pages[] and flushes caches for a run of subpages, and converts the three existing per-page call sites to use it with nr == 1, no functional change.

Patch 4 has follow_huge_pud()/follow_huge_pmd() report the huge page's real subpage count instead of a separate *page_mask output, and retires *page_mask and __get_user_pages()'s dead second try_grab_folio() call and subpage loop.

It also defers gup_fill_pages() past the pud/pmd unlock, so a 1 GB PUD-mapped folio doesn't hold that lock for a full array fill and cache flush.

Patch 5 adds follow_pte_batch() and has follow_page_pte() call it once per contiguous same-folio run instead of once per page, so a PTE-mapped mTHP no longer restarts the walk and re-takes the PTE lock per subpage.

This is the only patch that changes the number of page table walks or lock acquisitions.

Benchmarked with mm/gup_test.c (PIN_LONGTERM_BENCHMARK, pin_user_pages
+ FOLL_LONGTERM, 256 MB region, median of 16 runs, folio formation verified via the per-size anon_fault_alloc counter):

before after
4 kB base pages 2721 us 1198 us (2.3x)
64 kB mTHP 2929 us 201 us (14.6x)
2 MB THP 73 us 69 us (flat)

The 4 kB result comes entirely from patch 5 merging two separate try_grab_folio() calls and lock acquisitions into one; folio size and PTE batching play no part in it.

64 kB mTHP adds the walk-restart avoidance on top. 2 MB THP is unaffected, since follow_huge_pmd() already handled it in one call.

Patch 4's lock-hold-time change is a scalability argument, not a measured one -- it is not visible in this single-threaded benchmark.

Rik van Riel (5):
mm/gup: convert follow_page_mask() to return a long mm/gup: split follow_page_pte_commit() out of follow_page_pte() mm/gup: add gup_fill_pages() and use it
mm/gup: return a huge page's full count from follow_page_mask() mm/gup: walk multiple PTEs per follow_page_pte() call

 mm/gup.c | 532 +++++++++++++++++++++++++++++++++----------------------
 1 file changed, 322 insertions(+), 210 deletions(-)

base-commit: fc02acf6ac0c

Signature · 1 line
2.53.0-Meta