History

mm: switch deferred split shrinker to list_lru

The deferred split queue handles cgroups in a suboptimal fashion. The queue is per-NUMA node or per-cgroup, not the intersection. That means on a cgrouped system, a node-restricted allocation entering reclaim can end up splitting large pages on other nodes: alloc/unmap deferred_split_folio() list_add_tail(memcg->split_queue) set_shrinker_bit(memcg, node, deferred_shrinker_id) for_each_zone_zonelist_nodemask(restricted_nodes) mem_cgroup_iter() shrink_slab(node, memcg) shrink_slab_memcg(node, memcg) if test_shrinker_bit(memcg, node, deferred_shrinker_id) deferred_split_scan() walks memcg->split_queue The shrinker bit adds an imperfect guard rail. As soon as the cgroup has a single large page on the node of interest, all large pages owned by that memcg, including those on other nodes, will be split. list_lru properly sets up per-node, per-cgroup lists. As a bonus, it streamlines a lot of the list operations and reclaim walks. It's used widely by other major shrinkers already. Convert the deferred split queue as well. The list_lru per-memcg heads are instantiated on demand when the first object of interest is allocated for a cgroup, by calling folio_memcg_alloc_deferred(). Add calls to where splittable pages are created: anon faults, swapin faults, khugepaged collapse. These calls create all possible node heads for the cgroup at once, so the migration code (between nodes) doesn't need any special care. [akpm@linux-foundation.org: fix build with CONFIG_TRANSPARENT_HUGEPAGE=n] Link: https://lore.kernel.org/202605281620.lc3rtkBm-lkp@intel.com [hannes@cmpxchg.org: fix cgroup.memory=nokmem handling] Link: https://lore.kernel.org/ah9PGv12mqai84ES@cmpxchg.org Link: https://lore.kernel.org/20260527204757.2544958-10-hannes@cmpxchg.org Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Reported-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Acked-by: Shakeel Butt <shakeel.butt@linux.dev> Reviewed-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org> Acked-by: Usama Arif <usama.arif@linux.dev> Reviewed-by: Kairui Song <kasong@tencent.com> Cc: Baolin Wang <baolin.wang@linux.alibaba.com> Cc: Barry Song <baohua@kernel.org> Cc: Dave Chinner <david@fromorbit.com> Cc: David Hildenbrand (Arm) <david@kernel.org> Cc: Dev Jain <dev.jain@arm.com> Cc: Lance Yang <lance.yang@linux.dev> Cc: Liam R. Howlett <liam@infradead.org> Cc: Michal Hocko <mhocko@kernel.org> Cc: Muchun Song <muchun.song@linux.dev> Cc: Nico Pache <npache@redhat.com> Cc: Roman Gushchin <roman.gushchin@linux.dev> Cc: Ryan Roberts <ryan.roberts@arm.com> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Vlastimil Babka <vbabka@kernel.org> Cc: Zi Yan <ziy@nvidia.com> Cc: kernel test robot <lkp@intel.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

Johannes Weiner committed 5 months ago

Commit fafaece1 parent 65180e910 files · +160 −288Browse files

Showing the changes to include (3 of 10 files). Show all

Unified · Split

3 files
  1. include/linux/huge_mm.h +12 −5
  2. include/linux/memcontrol.h +0 −4
  3. include/linux/mmzone.h +0 −12
include/linux/huge_mm.h+12−5
@@ -439,10 +439,10 @@ static inline int split_huge_page(struct page *page) · 438 unmodified lines
439439 {
440440 return split_huge_page_to_list_to_order(page, NULL, 0);
441441 }
442+
443+int folio_memcg_alloc_deferred(struct folio *folio);
444+
442445 void deferred_split_folio(struct folio *folio, bool partially_mapped);
443−#ifdef CONFIG_MEMCG
444−void reparent_deferred_split_queue(struct mem_cgroup *memcg);
445−#endif
446446
447447 void __split_huge_pmd(struct vm_area_struct *vma, pmd_t *pmd,
448448 unsigned long address, bool freeze);
@@ -679,8 +679,15 @@ static inline int try_folio_split_to_order(struct folio *folio, · 230 unmodified lines
679679 return -EINVAL;
680680 }
681681
682−static inline void deferred_split_folio(struct folio *folio, bool partially_mapped) {}
683−static inline void reparent_deferred_split_queue(struct mem_cgroup *memcg) {}
682+static inline int folio_memcg_alloc_deferred(struct folio *folio)
683+{
684+ return 0;
685+}
686+
687+static inline void deferred_split_folio(struct folio *folio, bool partially_mapped)
688+{
689+}
690+
684691 #define split_huge_pmd(__vma, __pmd, __address) \
685692 do { } while (0)
686693
include/linux/memcontrol.h+0−4
@@ -278,10 +278,6 @@ struct mem_cgroup { · 277 unmodified lines
278278 struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT];
279279 #endif
280280
281−#ifdef CONFIG_TRANSPARENT_HUGEPAGE
282− struct deferred_split deferred_split_queue;
283−#endif
284−
285281 #ifdef CONFIG_LRU_GEN_WALKS_MMU
286282 /* per-memcg mm_struct list */
287283 struct lru_gen_mm_list mm_list;
include/linux/mmzone.h+0−12
@@ -1431,14 +1431,6 @@ struct zonelist { · 1430 unmodified lines
14311431 */
14321432 extern struct page *mem_map;
14331433
1434−#ifdef CONFIG_TRANSPARENT_HUGEPAGE
1435−struct deferred_split {
1436− spinlock_t split_queue_lock;
1437− struct list_head split_queue;
1438− unsigned long split_queue_len;
1439−};
1440−#endif
1441−
14421434 #ifdef CONFIG_MEMORY_FAILURE
14431435 /*
14441436 * Per NUMA node memory failure handling statistics.
@@ -1564,10 +1556,6 @@ typedef struct pglist_data { · 119 unmodified lines
15641556 unsigned long first_deferred_pfn;
15651557 #endif /* CONFIG_DEFERRED_STRUCT_PAGE_INIT */
15661558
1567−#ifdef CONFIG_TRANSPARENT_HUGEPAGE
1568− struct deferred_split deferred_split_queue;
1569−#endif
1570−
15711559 #ifdef CONFIG_NUMA_BALANCING
15721560 /* start time in ms of current promote rate limit period */
15731561 unsigned int nbp_rl_start;