]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
mm: page_alloc: defrag_mode kswapd/kcompactd watermarks
authorJohannes Weiner <hannes@cmpxchg.org>
Thu, 13 Mar 2025 21:05:36 +0000 (17:05 -0400)
committerAndrew Morton <akpm@linux-foundation.org>
Tue, 18 Mar 2025 05:07:07 +0000 (22:07 -0700)
The previous patch added pageblock_order reclaim to kswapd/kcompactd,
which helps, but produces only one block at a time.  Allocation stalls and
THP failure rates are still higher than they could be.

To adequately reflect ALLOC_NOFRAGMENT demand for pageblocks, change the
watermarking for kswapd & kcompactd: instead of targeting the high
watermark in order-0 pages and checking for one suitable block, simply
require that the high watermark is entirely met in pageblocks.

To this end, track the number of free pages within contiguous pageblocks,
then change pgdat_balanced() and compact_finished() to check watermarks
against this new value.

This further reduces THP latencies and allocation stalls, and improves THP
success rates against the previous patch:

                                       DEFRAGMODE-ASYNC DEFRAGMODE-ASYNC-WMARKS
Hugealloc Time mean               34300.36 (    +0.00%)   28904.00 (   -15.73%)
Hugealloc Time stddev             36390.42 (    +0.00%)   33464.37 (    -8.04%)
Kbuild Real time                    196.13 (    +0.00%)     196.59 (    +0.23%)
Kbuild User time                   1234.74 (    +0.00%)    1231.67 (    -0.25%)
Kbuild System time                   62.62 (    +0.00%)      59.10 (    -5.54%)
THP fault alloc                   57054.53 (    +0.00%)   63223.67 (   +10.81%)
THP fault fallback                11581.40 (    +0.00%)    5412.47 (   -53.26%)
Direct compact fail                 107.80 (    +0.00%)      59.07 (   -44.79%)
Direct compact success                4.53 (    +0.00%)       2.80 (   -31.33%)
Direct compact success rate %         3.20 (    +0.00%)       3.99 (   +18.66%)
Compact daemon scanned migrate  5461033.93 (    +0.00%) 2267500.33 (   -58.48%)
Compact daemon scanned free     5824897.93 (    +0.00%) 2339773.00 (   -59.83%)
Compact direct scanned migrate    58336.93 (    +0.00%)   47659.93 (   -18.30%)
Compact direct scanned free       32791.87 (    +0.00%)   40729.67 (   +24.21%)
Compact total migrate scanned   5519370.87 (    +0.00%) 2315160.27 (   -58.05%)
Compact total free scanned      5857689.80 (    +0.00%) 2380502.67 (   -59.36%)
Alloc stall                        2424.60 (    +0.00%)     638.87 (   -73.62%)
Pages kswapd scanned            2657018.33 (    +0.00%) 4002186.33 (   +50.63%)
Pages kswapd reclaimed           559583.07 (    +0.00%)  718577.80 (   +28.41%)
Pages direct scanned             722094.07 (    +0.00%)  355172.73 (   -50.81%)
Pages direct reclaimed           107257.80 (    +0.00%)   31162.80 (   -70.95%)
Pages total scanned             3379112.40 (    +0.00%) 4357359.07 (   +28.95%)
Pages total reclaimed            666840.87 (    +0.00%)  749740.60 (   +12.43%)
Swap out                          77238.20 (    +0.00%)  110084.33 (   +42.53%)
Swap in                           11712.80 (    +0.00%)   24457.00 (  +108.80%)
File refaults                    143438.80 (    +0.00%)  188226.93 (   +31.22%)

Also of note is that compaction work overall is reduced.  The reason for
this is that when free pageblocks are more readily available, allocations
are also much more likely to get physically placed in LRU order, instead
of being forced to scavenge free space here and there.  This means that
reclaim by itself has better chances of freeing up whole blocks, and the
system relies less on compaction.

Comparing all changes to the vanilla kernel:

                                                VANILLA DEFRAGMODE-ASYNC-WMARKS
Hugealloc Time mean               52739.45 (    +0.00%)   28904.00 (   -45.19%)
Hugealloc Time stddev             56541.26 (    +0.00%)   33464.37 (   -40.81%)
Kbuild Real time                    197.47 (    +0.00%)     196.59 (    -0.44%)
Kbuild User time                   1240.49 (    +0.00%)    1231.67 (    -0.71%)
Kbuild System time                   70.08 (    +0.00%)      59.10 (   -15.45%)
THP fault alloc                   46727.07 (    +0.00%)   63223.67 (   +35.30%)
THP fault fallback                21910.60 (    +0.00%)    5412.47 (   -75.29%)
Direct compact fail                 195.80 (    +0.00%)      59.07 (   -69.48%)
Direct compact success                7.93 (    +0.00%)       2.80 (   -57.46%)
Direct compact success rate %         3.51 (    +0.00%)       3.99 (   +10.49%)
Compact daemon scanned migrate  3369601.27 (    +0.00%) 2267500.33 (   -32.71%)
Compact daemon scanned free     5075474.47 (    +0.00%) 2339773.00 (   -53.90%)
Compact direct scanned migrate   161787.27 (    +0.00%)   47659.93 (   -70.54%)
Compact direct scanned free      163467.53 (    +0.00%)   40729.67 (   -75.08%)
Compact total migrate scanned   3531388.53 (    +0.00%) 2315160.27 (   -34.44%)
Compact total free scanned      5238942.00 (    +0.00%) 2380502.67 (   -54.56%)
Alloc stall                        2371.07 (    +0.00%)     638.87 (   -73.02%)
Pages kswapd scanned            2160926.73 (    +0.00%) 4002186.33 (   +85.21%)
Pages kswapd reclaimed           533191.07 (    +0.00%)  718577.80 (   +34.77%)
Pages direct scanned             400450.33 (    +0.00%)  355172.73 (   -11.31%)
Pages direct reclaimed            94441.73 (    +0.00%)   31162.80 (   -67.00%)
Pages total scanned             2561377.07 (    +0.00%) 4357359.07 (   +70.12%)
Pages total reclaimed            627632.80 (    +0.00%)  749740.60 (   +19.46%)
Swap out                          47959.53 (    +0.00%)  110084.33 (  +129.53%)
Swap in                            7276.00 (    +0.00%)   24457.00 (  +236.10%)
File refaults                    138043.00 (    +0.00%)  188226.93 (   +36.35%)

THP allocation latencies and %sys time are down dramatically.

THP allocation failures are down from nearly 50% to 8.5%.  And to recall
previous data points, the success rates are steady and reliable without
the cumulative deterioration of fragmentation events.

Compaction work is down overall.  Direct compaction work especially is
drastically reduced.  As an aside, its success rate of 4% indicates there
is room for improvement.  For now it's good to rely on it less.

Reclaim work is up overall, however direct reclaim work is down.  Part of
the increase can be attributed to a higher use of THPs, which due to
internal fragmentation increase the memory footprint.  This is not
necessarily an unexpected side-effect for users of THP.

However, taken both points together, there may well be some opportunities
for fine tuning in the reclaim/compaction coordination.

[hannes@cmpxchg.org: fix squawks from rebasing]
Link: https://lkml.kernel.org/r/20250314210558.GD1316033@cmpxchg.org
Link: https://lkml.kernel.org/r/20250313210647.1314586-6-hannes@cmpxchg.org
Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
Cc: Mel Gorman <mgorman@techsingularity.net>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Zi Yan <ziy@nvidia.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
include/linux/mmzone.h
mm/compaction.c
mm/internal.h
mm/page_alloc.c
mm/vmscan.c
mm/vmstat.c

index dbb0ad69e17f2a7cdf9a36b9d0f92f149fbea611..37c29f3fbca839c735934ead3a8f4f13bf8b8ba7 100644 (file)
@@ -138,6 +138,7 @@ enum numa_stat_item {
 enum zone_stat_item {
        /* First 128 byte cacheline (assuming 64 bit words) */
        NR_FREE_PAGES,
+       NR_FREE_PAGES_BLOCKS,
        NR_ZONE_LRU_BASE, /* Used only for compaction and reclaim retry */
        NR_ZONE_INACTIVE_ANON = NR_ZONE_LRU_BASE,
        NR_ZONE_ACTIVE_ANON,
index cf32e8053edbad1f2444732cd54788d10a434be3..139f00c0308a3d43ae3ed445eeed899f95d0c353 100644 (file)
@@ -2328,6 +2328,22 @@ static enum compact_result __compact_finished(struct compact_control *cc)
        if (!pageblock_aligned(cc->migrate_pfn))
                return COMPACT_CONTINUE;
 
+       /*
+        * When defrag_mode is enabled, make kcompactd target
+        * watermarks in whole pageblocks. Because they can be stolen
+        * without polluting, no further fallback checks are needed.
+        */
+       if (defrag_mode && !cc->direct_compaction) {
+               if (__zone_watermark_ok(cc->zone, cc->order,
+                                       high_wmark_pages(cc->zone),
+                                       cc->highest_zoneidx, cc->alloc_flags,
+                                       zone_page_state(cc->zone,
+                                                       NR_FREE_PAGES_BLOCKS)))
+                       return COMPACT_SUCCESS;
+
+               return COMPACT_CONTINUE;
+       }
+
        /* Direct compactor: Is a suitable page free? */
        ret = COMPACT_NO_SUITABLE_PAGE;
        for (order = cc->order; order < NR_PAGE_ORDERS; order++) {
@@ -2495,13 +2511,19 @@ bool compaction_zonelist_suitable(struct alloc_context *ac, int order,
 static enum compact_result
 compaction_suit_allocation_order(struct zone *zone, unsigned int order,
                                 int highest_zoneidx, unsigned int alloc_flags,
-                                bool async)
+                                bool async, bool kcompactd)
 {
+       unsigned long free_pages;
        unsigned long watermark;
 
+       if (kcompactd && defrag_mode)
+               free_pages = zone_page_state(zone, NR_FREE_PAGES_BLOCKS);
+       else
+               free_pages = zone_page_state(zone, NR_FREE_PAGES);
+
        watermark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK);
-       if (zone_watermark_ok(zone, order, watermark, highest_zoneidx,
-                             alloc_flags))
+       if (__zone_watermark_ok(zone, order, watermark, highest_zoneidx,
+                               alloc_flags, free_pages))
                return COMPACT_SUCCESS;
 
        /*
@@ -2557,7 +2579,8 @@ compact_zone(struct compact_control *cc, struct capture_control *capc)
                ret = compaction_suit_allocation_order(cc->zone, cc->order,
                                                       cc->highest_zoneidx,
                                                       cc->alloc_flags,
-                                                      cc->mode == MIGRATE_ASYNC);
+                                                      cc->mode == MIGRATE_ASYNC,
+                                                      !cc->direct_compaction);
                if (ret != COMPACT_CONTINUE)
                        return ret;
        }
@@ -3051,6 +3074,8 @@ static bool kcompactd_node_suitable(pg_data_t *pgdat)
        struct zone *zone;
        enum zone_type highest_zoneidx = pgdat->kcompactd_highest_zoneidx;
        enum compact_result ret;
+       unsigned int alloc_flags = defrag_mode ?
+               ALLOC_WMARK_HIGH : ALLOC_WMARK_MIN;
 
        for (zoneid = 0; zoneid <= highest_zoneidx; zoneid++) {
                zone = &pgdat->node_zones[zoneid];
@@ -3060,8 +3085,8 @@ static bool kcompactd_node_suitable(pg_data_t *pgdat)
 
                ret = compaction_suit_allocation_order(zone,
                                pgdat->kcompactd_max_order,
-                               highest_zoneidx, ALLOC_WMARK_MIN,
-                               false);
+                               highest_zoneidx, alloc_flags,
+                               false, true);
                if (ret == COMPACT_CONTINUE)
                        return true;
        }
@@ -3084,7 +3109,7 @@ static void kcompactd_do_work(pg_data_t *pgdat)
                .mode = MIGRATE_SYNC_LIGHT,
                .ignore_skip_hint = false,
                .gfp_mask = GFP_KERNEL,
-               .alloc_flags = ALLOC_WMARK_MIN,
+               .alloc_flags = defrag_mode ? ALLOC_WMARK_HIGH : ALLOC_WMARK_MIN,
        };
        enum compact_result ret;
 
@@ -3104,7 +3129,7 @@ static void kcompactd_do_work(pg_data_t *pgdat)
 
                ret = compaction_suit_allocation_order(zone,
                                cc.order, zoneid, cc.alloc_flags,
-                               false);
+                               false, true);
                if (ret != COMPACT_CONTINUE)
                        continue;
 
index 2f52a65272c1969e2ad619caa17d28d5390eafb9..286520a424fe6e032fb72d989b68a5bec0a0aa55 100644 (file)
@@ -536,6 +536,7 @@ extern char * const zone_names[MAX_NR_ZONES];
 DECLARE_STATIC_KEY_MAYBE(CONFIG_DEBUG_VM, check_pages_enabled);
 
 extern int min_free_kbytes;
+extern int defrag_mode;
 
 void setup_per_zone_wmarks(void);
 void calculate_min_free_kbytes(void);
index 5a2ee82f723e3bcdaf633f28518a1a1932082094..4337467eaf5a730f86221e3d1a02e43cbdae18ae 100644 (file)
@@ -273,7 +273,7 @@ int min_free_kbytes = 1024;
 int user_min_free_kbytes = -1;
 static int watermark_boost_factor __read_mostly = 15000;
 static int watermark_scale_factor = 10;
-static int defrag_mode;
+int defrag_mode;
 
 /* movable_zone is the "real" zone pages in ZONE_MOVABLE are taken from */
 int movable_zone;
@@ -660,16 +660,20 @@ static inline void __add_to_free_list(struct page *page, struct zone *zone,
                                      bool tail)
 {
        struct free_area *area = &zone->free_area[order];
+       int nr_pages = 1 << order;
 
        VM_WARN_ONCE(get_pageblock_migratetype(page) != migratetype,
                     "page type is %lu, passed migratetype is %d (nr=%d)\n",
-                    get_pageblock_migratetype(page), migratetype, 1 << order);
+                    get_pageblock_migratetype(page), migratetype, nr_pages);
 
        if (tail)
                list_add_tail(&page->buddy_list, &area->free_list[migratetype]);
        else
                list_add(&page->buddy_list, &area->free_list[migratetype]);
        area->nr_free++;
+
+       if (order >= pageblock_order && !is_migrate_isolate(migratetype))
+               __mod_zone_page_state(zone, NR_FREE_PAGES_BLOCKS, nr_pages);
 }
 
 /*
@@ -681,24 +685,34 @@ static inline void move_to_free_list(struct page *page, struct zone *zone,
                                     unsigned int order, int old_mt, int new_mt)
 {
        struct free_area *area = &zone->free_area[order];
+       int nr_pages = 1 << order;
 
        /* Free page moving can fail, so it happens before the type update */
        VM_WARN_ONCE(get_pageblock_migratetype(page) != old_mt,
                     "page type is %lu, passed migratetype is %d (nr=%d)\n",
-                    get_pageblock_migratetype(page), old_mt, 1 << order);
+                    get_pageblock_migratetype(page), old_mt, nr_pages);
 
        list_move_tail(&page->buddy_list, &area->free_list[new_mt]);
 
-       account_freepages(zone, -(1 << order), old_mt);
-       account_freepages(zone, 1 << order, new_mt);
+       account_freepages(zone, -nr_pages, old_mt);
+       account_freepages(zone, nr_pages, new_mt);
+
+       if (order >= pageblock_order &&
+           is_migrate_isolate(old_mt) != is_migrate_isolate(new_mt)) {
+               if (!is_migrate_isolate(old_mt))
+                       nr_pages = -nr_pages;
+               __mod_zone_page_state(zone, NR_FREE_PAGES_BLOCKS, nr_pages);
+       }
 }
 
 static inline void __del_page_from_free_list(struct page *page, struct zone *zone,
                                             unsigned int order, int migratetype)
 {
+       int nr_pages = 1 << order;
+
         VM_WARN_ONCE(get_pageblock_migratetype(page) != migratetype,
                     "page type is %lu, passed migratetype is %d (nr=%d)\n",
-                    get_pageblock_migratetype(page), migratetype, 1 << order);
+                    get_pageblock_migratetype(page), migratetype, nr_pages);
 
        /* clear reported state and update reported page count */
        if (page_reported(page))
@@ -708,6 +722,9 @@ static inline void __del_page_from_free_list(struct page *page, struct zone *zon
        __ClearPageBuddy(page);
        set_page_private(page, 0);
        zone->free_area[order].nr_free--;
+
+       if (order >= pageblock_order && !is_migrate_isolate(migratetype))
+               __mod_zone_page_state(zone, NR_FREE_PAGES_BLOCKS, -nr_pages);
 }
 
 static inline void del_page_from_free_list(struct page *page, struct zone *zone,
index 3370bdca6868c10c2960e5201dd1691cd2ab4e43..b5c7dfc2b18999c8a0b02c0811af3089d54e79e8 100644 (file)
@@ -6724,11 +6724,24 @@ static bool pgdat_balanced(pg_data_t *pgdat, int order, int highest_zoneidx)
         * meet watermarks.
         */
        for_each_managed_zone_pgdat(zone, pgdat, i, highest_zoneidx) {
+               unsigned long free_pages;
+
                if (sysctl_numa_balancing_mode & NUMA_BALANCING_MEMORY_TIERING)
                        mark = promo_wmark_pages(zone);
                else
                        mark = high_wmark_pages(zone);
-               if (zone_watermark_ok_safe(zone, order, mark, highest_zoneidx))
+
+               /*
+                * In defrag_mode, watermarks must be met in whole
+                * blocks to avoid polluting allocator fallbacks.
+                */
+               if (defrag_mode)
+                       free_pages = zone_page_state(zone, NR_FREE_PAGES_BLOCKS);
+               else
+                       free_pages = zone_page_state(zone, NR_FREE_PAGES);
+
+               if (__zone_watermark_ok(zone, order, mark, highest_zoneidx,
+                                       0, free_pages))
                        return true;
        }
 
index 16bfe1c694dd4e0c98b39337a367a2ea86e8f3bd..ed49a86348f7af95007318040afdf09155f1ecd4 100644 (file)
@@ -1190,6 +1190,7 @@ int fragmentation_index(struct zone *zone, unsigned int order)
 const char * const vmstat_text[] = {
        /* enum zone_stat_item counters */
        "nr_free_pages",
+       "nr_free_pages_blocks",
        "nr_zone_inactive_anon",
        "nr_zone_active_anon",
        "nr_zone_inactive_file",