Skip to content

Commit c2f6ea3

Browse files
hnazakpm00
authored andcommitted
mm: page_alloc: don't steal single pages from biggest buddy
The fallback code searches for the biggest buddy first in an attempt to steal the whole block and encourage type grouping down the line. The approach used to be this: - Non-movable requests will split the largest buddy and steal the remainder. This splits up contiguity, but it allows subsequent requests of this type to fall back into adjacent space. - Movable requests go and look for the smallest buddy instead. The thinking is that movable requests can be compacted, so grouping is less important than retaining contiguity. c0cd6f5 ("mm: page_alloc: fix freelist movement during block conversion") enforces freelist type hygiene, which restricts stealing to either claiming the whole block or just taking the requested chunk; no additional pages or buddy remainders can be stolen any more. The patch mishandled when to switch to finding the smallest buddy in that new reality. As a result, it may steal the exact request size, but from the biggest buddy. This causes fracturing for no good reason. Fix this by committing to the new behavior: either steal the whole block, or fall back to the smallest buddy. Remove single-page stealing from steal_suitable_fallback(). Rename it to try_to_steal_block() to make the intentions clear. If this fails, always fall back to the smallest buddy. The following is from 4 runs of mmtest's thpchallenge. "Pollute" is single page fallback, "steal" is conversion of a partially used block. The numbers for free block conversions (omitted) are comparable. vanilla patched @pollute[unmovable from reclaimable]: 27 106 @pollute[unmovable from movable]: 82 46 @pollute[reclaimable from unmovable]: 256 83 @pollute[reclaimable from movable]: 46 8 @pollute[movable from unmovable]: 4841 868 @pollute[movable from reclaimable]: 5278 12568 @steal[unmovable from reclaimable]: 11 12 @steal[unmovable from movable]: 113 49 @steal[reclaimable from unmovable]: 19 34 @steal[reclaimable from movable]: 47 21 @steal[movable from unmovable]: 250 183 @steal[movable from reclaimable]: 81 93 The allocator appears to do a better job at keeping stealing and polluting to the first fallback preference. As a result, the numbers for "from movable" - the least preferred fallback option, and most detrimental to compactability - are down across the board. Link: https://lkml.kernel.org/r/20250225001023.1494422-2-hannes@cmpxchg.org Fixes: c0cd6f5 ("mm: page_alloc: fix freelist movement during block conversion") Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Suggested-by: Vlastimil Babka <vbabka@suse.cz> Reviewed-by: Brendan Jackman <jackmanb@google.com> Reviewed-by: Vlastimil Babka <vbabka@suse.cz> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
1 parent f3b9217 commit c2f6ea3

1 file changed

Lines changed: 34 additions & 46 deletions

File tree

mm/page_alloc.c

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,13 +1986,12 @@ static inline bool boost_watermark(struct zone *zone)
19861986
* can claim the whole pageblock for the requested migratetype. If not, we check
19871987
* the pageblock for constituent pages; if at least half of the pages are free
19881988
* or compatible, we can still claim the whole block, so pages freed in the
1989-
* future will be put on the correct free list. Otherwise, we isolate exactly
1990-
* the order we need from the fallback block and leave its migratetype alone.
1989+
* future will be put on the correct free list.
19911990
*/
19921991
static struct page *
1993-
steal_suitable_fallback(struct zone *zone, struct page *page,
1994-
int current_order, int order, int start_type,
1995-
unsigned int alloc_flags, bool whole_block)
1992+
try_to_steal_block(struct zone *zone, struct page *page,
1993+
int current_order, int order, int start_type,
1994+
unsigned int alloc_flags)
19961995
{
19971996
int free_pages, movable_pages, alike_pages;
19981997
unsigned long start_pfn;
@@ -2005,7 +2004,7 @@ steal_suitable_fallback(struct zone *zone, struct page *page,
20052004
* highatomic accounting.
20062005
*/
20072006
if (is_migrate_highatomic(block_type))
2008-
goto single_page;
2007+
return NULL;
20092008

20102009
/* Take ownership for orders >= pageblock_order */
20112010
if (current_order >= pageblock_order) {
@@ -2026,14 +2025,10 @@ steal_suitable_fallback(struct zone *zone, struct page *page,
20262025
if (boost_watermark(zone) && (alloc_flags & ALLOC_KSWAPD))
20272026
set_bit(ZONE_BOOSTED_WATERMARK, &zone->flags);
20282027

2029-
/* We are not allowed to try stealing from the whole block */
2030-
if (!whole_block)
2031-
goto single_page;
2032-
20332028
/* moving whole block can fail due to zone boundary conditions */
20342029
if (!prep_move_freepages_block(zone, page, &start_pfn, &free_pages,
20352030
&movable_pages))
2036-
goto single_page;
2031+
return NULL;
20372032

20382033
/*
20392034
* Determine how many pages are compatible with our allocation.
@@ -2066,9 +2061,7 @@ steal_suitable_fallback(struct zone *zone, struct page *page,
20662061
return __rmqueue_smallest(zone, order, start_type);
20672062
}
20682063

2069-
single_page:
2070-
page_del_and_expand(zone, page, order, current_order, block_type);
2071-
return page;
2064+
return NULL;
20722065
}
20732066

20742067
/*
@@ -2250,14 +2243,19 @@ static bool unreserve_highatomic_pageblock(const struct alloc_context *ac,
22502243
}
22512244

22522245
/*
2253-
* Try finding a free buddy page on the fallback list and put it on the free
2254-
* list of requested migratetype, possibly along with other pages from the same
2255-
* block, depending on fragmentation avoidance heuristics. Returns true if
2256-
* fallback was found so that __rmqueue_smallest() can grab it.
2246+
* Try finding a free buddy page on the fallback list.
2247+
*
2248+
* This will attempt to steal a whole pageblock for the requested type
2249+
* to ensure grouping of such requests in the future.
2250+
*
2251+
* If a whole block cannot be stolen, regress to __rmqueue_smallest()
2252+
* logic to at least break up as little contiguity as possible.
22572253
*
22582254
* The use of signed ints for order and current_order is a deliberate
22592255
* deviation from the rest of this file, to make the for loop
22602256
* condition simpler.
2257+
*
2258+
* Return the stolen page, or NULL if none can be found.
22612259
*/
22622260
static __always_inline struct page *
22632261
__rmqueue_fallback(struct zone *zone, int order, int start_migratetype,
@@ -2291,45 +2289,35 @@ __rmqueue_fallback(struct zone *zone, int order, int start_migratetype,
22912289
if (fallback_mt == -1)
22922290
continue;
22932291

2294-
/*
2295-
* We cannot steal all free pages from the pageblock and the
2296-
* requested migratetype is movable. In that case it's better to
2297-
* steal and split the smallest available page instead of the
2298-
* largest available page, because even if the next movable
2299-
* allocation falls back into a different pageblock than this
2300-
* one, it won't cause permanent fragmentation.
2301-
*/
2302-
if (!can_steal && start_migratetype == MIGRATE_MOVABLE
2303-
&& current_order > order)
2304-
goto find_smallest;
2292+
if (!can_steal)
2293+
break;
23052294

2306-
goto do_steal;
2295+
page = get_page_from_free_area(area, fallback_mt);
2296+
page = try_to_steal_block(zone, page, current_order, order,
2297+
start_migratetype, alloc_flags);
2298+
if (page)
2299+
goto got_one;
23072300
}
23082301

2309-
return NULL;
2302+
if (alloc_flags & ALLOC_NOFRAGMENT)
2303+
return NULL;
23102304

2311-
find_smallest:
2305+
/* No luck stealing blocks. Find the smallest fallback page */
23122306
for (current_order = order; current_order < NR_PAGE_ORDERS; current_order++) {
23132307
area = &(zone->free_area[current_order]);
23142308
fallback_mt = find_suitable_fallback(area, current_order,
23152309
start_migratetype, false, &can_steal);
2316-
if (fallback_mt != -1)
2317-
break;
2318-
}
2319-
2320-
/*
2321-
* This should not happen - we already found a suitable fallback
2322-
* when looking for the largest page.
2323-
*/
2324-
VM_BUG_ON(current_order > MAX_PAGE_ORDER);
2310+
if (fallback_mt == -1)
2311+
continue;
23252312

2326-
do_steal:
2327-
page = get_page_from_free_area(area, fallback_mt);
2313+
page = get_page_from_free_area(area, fallback_mt);
2314+
page_del_and_expand(zone, page, order, current_order, fallback_mt);
2315+
goto got_one;
2316+
}
23282317

2329-
/* take off list, maybe claim block, expand remainder */
2330-
page = steal_suitable_fallback(zone, page, current_order, order,
2331-
start_migratetype, alloc_flags, can_steal);
2318+
return NULL;
23322319

2320+
got_one:
23332321
trace_mm_page_alloc_extfrag(page, order, current_order,
23342322
start_migratetype, fallback_mt);
23352323

0 commit comments

Comments
 (0)