mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Andrew Morton <akpm@linux-foundation.org>
To: "Barry Song (Xiaomi)" <baohua@kernel.org>
Cc: linux-mm@kvack.org, axelrasmussen@google.com,
	baolin.wang@linux.alibaba.com, baoquan.he@linux.dev,
	hannes@cmpxchg.org, hui.zhu@linux.dev, kasong@tencent.com,
	kunwu.chan@gmail.com, liam@infradead.org, lianux.mm@gmail.com,
	linux-kernel@vger.kernel.org, liuxiang.277@bytedance.com,
	lyugaofei@xiaomi.com, mhocko@kernel.org, mhocko@suse.com,
	qi.zheng@linux.dev, ridong.chen@linux.dev, rppt@kernel.org,
	ryncsn@gmail.com, shakeel.butt@linux.dev, surenb@google.com,
	wangzicheng@honor.com, weixugc@google.com, yuanchu@google.com,
	zhangbo56@xiaomi.com, zhuhui@kylinos.cn,
	sidchintamaneni@gmail.com
Subject: Re: [PATCH v9 00/16] mm: mglru: fix swappiness behavior
Date: Sat, 14 Nov 2026 14:09:49 -0800	[thread overview]
Message-ID: <20261114140949.c47f9cf9c1ea22315092d880@linux-foundation.org> (raw)
In-Reply-To: <20261114200803.23129-1-baohua@kernel.org>

On Sun, 15 Nov 2026 04:07:47 +0800 "Barry Song (Xiaomi)" <baohua@kernel.org> wrote:

> Classic LRU respects swappiness, while MGLRU only weakly
> honors it when selecting the scan type. The fallback logic
> can easily override the bias established by
> get_type_to_scan(). As a result, swappiness has a much
> smaller overall impact when changing from 1 to 200, as
> shown by the data presented later in this cover letter.
> 
> In this patchset, patches 1-8 from Kairui introduce a model that uses
> refaults to determine nr_to_scan[] for both file and anon in MGLRU.
>
> ...
>

Thanks, I updated mm.git to this version.  I included that Sashiko
fixup.

The patchset does seem to be changing pretty quickly so I'll continue
to hold it in the mm-new branch until things have settled down.

> v9:
> * reduce one iteration for lruvec_populated_min_seq();
> * let lruvec_is_reclaimable() count all zones, for min_ttl;
> * refine lru_gen_imbalanced(), AGING_PRIORITY macro, roundup/down swappiness;
>   per Kairui;
> * let evict_folio_lists() return "drained" per Kairui, thanks!
> * one bounded retry for scanned=0 and reduce concurrent aging
>   for no-populatable(drained) aging;

Here's how v9 altered mm.git:

 mm/vmscan.c |  131 ++++++++++++++++++++++++--------------------------
 mm/vmscan.h |    2 
 2 files changed, 66 insertions(+), 67 deletions(-)

--- a/mm/vmscan.c~b
+++ a/mm/vmscan.c
@@ -2736,6 +2736,8 @@ static bool should_clear_pmd_young(void)
 #define get_memcg_gen(seq)	((seq) % MEMCG_NR_GENS)
 #define get_memcg_bin(bin)	((bin) % MEMCG_NR_BINS)
 
+#define AGING_PRIORITY	(DEF_PRIORITY / 2)
+
 static int get_swappiness(struct lruvec *lruvec, struct scan_control *sc)
 {
 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
@@ -3995,7 +3997,7 @@ static unsigned long lruvec_populated_mi
 	lockdep_assert_held(&lruvec->lru_lock);
 
 	min_seq = READ_ONCE(lrugen->min_seq[type]);
-	while (min_seq <= max_seq) {
+	while (min_seq < max_seq) {
 		gen = lru_gen_from_seq(min_seq);
 		for (zone = 0; zone < zone_limit; zone++) {
 			if (!list_empty(&lrugen->folios[gen][type][zone]))
@@ -4199,7 +4201,8 @@ static void set_initial_priority(struct
 }
 
 static void lruvec_evictable_size(struct lruvec *lruvec, struct scan_control *sc,
-				  int swappiness, unsigned long nr[])
+				  int swappiness, unsigned long nr[],
+				  int max_zone)
 {
 	int type;
 	unsigned long seq;
@@ -4216,16 +4219,17 @@ static void lruvec_evictable_size(struct
 		if (type == LRU_GEN_ANON && !can_reclaim_anon_pages(memcg, nid, sc))
 			continue;
 		for (seq = min_seq[type]; seq <= max_seq; seq++)
-			nr[type] += lruvec_gen_size(lrugen, type, seq, sc->reclaim_idx + 1);
+			nr[type] += lruvec_gen_size(lrugen, type, seq, max_zone);
 	}
 }
 
-static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc)
+static bool lruvec_is_sizable(struct lruvec *lruvec, struct scan_control *sc,
+		int max_zone)
 {
 	unsigned long total, sizes[ANON_AND_FILE];
 	int swappiness = get_swappiness(lruvec, sc);
 
-	lruvec_evictable_size(lruvec, sc, swappiness, sizes);
+	lruvec_evictable_size(lruvec, sc, swappiness, sizes, max_zone);
 	total = sizes[LRU_GEN_ANON] + sizes[LRU_GEN_FILE];
 
 	/* whether the size is big enough to be helpful */
@@ -4244,13 +4248,8 @@ static bool lruvec_is_reclaimable(struct
 	if (mem_cgroup_below_min(NULL, memcg))
 		return false;
 
-	/*
-	 * As in __alloc_pages_may_oom(), avoid triggering OOM for low-zone
-	 * allocations. Also, defer the LRU size check until reclaim reaches
-	 * a very low priority to avoid premature OOM.
-	 */
-	if (sc->priority <= 1 && sc->reclaim_idx >= ZONE_NORMAL &&
-	    !lruvec_is_sizable(lruvec, sc))
+	/* counting all zones to avoid premature OOM */
+	if (!lruvec_is_sizable(lruvec, sc, MAX_NR_ZONES))
 		return false;
 
 	gen = lru_gen_from_seq(evictable_min_seq(min_seq, swappiness));
@@ -4783,12 +4782,15 @@ static int scan_folios(unsigned long nr_
 	 */
 	if (sc->priority == DEF_PRIORITY)
 		seq = lrugen->max_seq - MIN_NR_GENS;
-	else if (sc->priority > DEF_PRIORITY / 2)
+	else if (sc->priority > AGING_PRIORITY)
 		seq = lrugen->max_seq - 1;
 	else
 		seq = lrugen->max_seq;
 
 	seq = lruvec_populated_min_seq(lruvec, type, sc->reclaim_idx + 1, seq);
+	if (seq < lrugen->min_seq[type])
+		return 0;
+
 	*scanned_seq = seq;
 	gen = lru_gen_from_seq(seq);
 	max_gen = lru_gen_from_seq(max_seq);
@@ -4893,8 +4895,8 @@ static int get_tier_idx(struct lruvec *l
  * r_file / r_anon: refault rate for file / anon.
  * w_file / w_anon: LRU size weight of file / anon.
  *
- * We don't calculate the scan cost, MGLRU uses lazy promotion, which will
- * cancel the scan budget natually.
+ * We don't calculate the scan cost. MGLRU uses lazy promotion, which will
+ * cancel the scan budget naturally.
  */
 static void lru_gen_balance_scan(struct lruvec *lruvec, struct scan_control *sc,
 		int swappiness, unsigned long total_scan, unsigned long nr[])
@@ -4950,12 +4952,12 @@ static void lru_gen_balance_scan(struct
 	file_weight = nr[LRU_GEN_FILE] + nr[LRU_GEN_ANON] / size_bias;
 
 	if (swappiness == MIN_SWAPPINESS + 1)
-		anon_factor = sc->priority <= DEF_PRIORITY / 2 ?
+		anon_factor = sc->priority <= AGING_PRIORITY ?
 			      refault_rate[LRU_GEN_FILE] * anon_weight : 0;
 	else
 		anon_factor = (u64)swappiness * refault_rate[LRU_GEN_FILE] * anon_weight;
 	if (swappiness == MAX_SWAPPINESS)
-		file_factor = sc->priority < DEF_PRIORITY / 2 ?
+		file_factor = sc->priority < AGING_PRIORITY ?
 			      refault_rate[LRU_GEN_ANON] * file_weight : 0;
 	else
 		file_factor = (u64)(MAX_SWAPPINESS - swappiness) *
@@ -5090,9 +5092,18 @@ static bool lru_gen_imbalanced(struct lr
 	DEFINE_MIN_SEQ(lruvec);
 	int type;
 
-	if (sc->priority > DEF_PRIORITY / 2)
+	if (sc->priority > AGING_PRIORITY)
 		return false;
 
+	/*
+	 * Round up or down corner swappiness, to balance the heavy biased
+	 * type better
+	 */
+	if (swappiness == MIN_SWAPPINESS + 1)
+		swappiness = MIN_SWAPPINESS;
+	else if (swappiness == MAX_SWAPPINESS)
+		swappiness = MAX_SWAPPINESS + 1;
+
 	for_each_evictable_type(type, swappiness) {
 		unsigned long seq;
 
@@ -5102,23 +5113,12 @@ static bool lru_gen_imbalanced(struct lr
 	}
 
 	/* the vast majority of pages sit in the youngest gen */
-	if (youngest[LRU_GEN_ANON] + youngest[LRU_GEN_FILE] >
-	    (old[LRU_GEN_ANON] + old[LRU_GEN_FILE]) * MAX_NR_GENS)
-		return true;
-
-	/* Swappiness extremes: the corresponding type matters */
-	if (swappiness == MIN_SWAPPINESS + 1)
-		return youngest[LRU_GEN_FILE] > old[LRU_GEN_FILE] * MAX_NR_GENS;
-
-	if (swappiness == MAX_SWAPPINESS)
-		return youngest[LRU_GEN_ANON] > old[LRU_GEN_ANON] * MAX_NR_GENS;
-
-	return false;
+	return youngest[LRU_GEN_ANON] + youngest[LRU_GEN_FILE] >
+	       (old[LRU_GEN_ANON] + old[LRU_GEN_FILE]) * MAX_NR_GENS;
 }
 
 static bool should_run_aging(struct lruvec *lruvec, unsigned long max_seq,
-			     struct scan_control *sc, int swappiness,
-			     bool no_populatable)
+			     struct scan_control *sc, int swappiness)
 {
 	DEFINE_MIN_SEQ(lruvec);
 
@@ -5130,13 +5130,6 @@ static bool should_run_aging(struct lruv
 	if (sc->priority == DEF_PRIORITY)
 		return false;
 
-	/*
-	 * Run aging if scan budget remains but no folios
-	 * in populatable generations are available to scan.
-	 */
-	if (no_populatable)
-		return true;
-
 	/* better to run aging even though eviction is still possible */
 	if (evictable_min_seq(min_seq, swappiness) + MIN_NR_GENS == max_seq)
 		return true;
@@ -5151,7 +5144,7 @@ static void lru_gen_prepare_scan(struct
 {
 	unsigned long total;
 
-	lruvec_evictable_size(lruvec, sc, swappiness, nr_to_scan);
+	lruvec_evictable_size(lruvec, sc, swappiness, nr_to_scan, sc->reclaim_idx + 1);
 	total = nr_to_scan[LRU_GEN_ANON] + nr_to_scan[LRU_GEN_FILE];
 
 	total = apply_proportional_protection(memcg, sc, total);
@@ -5190,7 +5183,8 @@ static bool should_abort_scan(struct lru
 }
 
 static long evict_folio_lists(unsigned long nr_to_scan[], struct lruvec *lruvec,
-			     struct scan_control *sc, int swappiness, unsigned long max_batch)
+			     struct scan_control *sc, int swappiness,
+			     unsigned long max_batch, bool *drained)
 {
 	int type;
 	unsigned long batch, scanned, total_scanned = 0;
@@ -5206,6 +5200,8 @@ static long evict_folio_lists(unsigned l
 		batch = min(nr_to_scan[type], max_batch);
 		scanned = evict_folios(batch, lruvec, sc, type, swappiness);
 
+		if (drained && !scanned)
+			*drained = true;
 		nr_to_scan[type] -= min(nr_to_scan[type], scanned);
 		total_scanned += scanned;
 	}
@@ -5221,16 +5217,15 @@ static long evict_folio_lists(unsigned l
 static bool try_to_shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
 {
 	unsigned long nr[ANON_AND_FILE];
-	bool need_rotate = false, should_age = false;
+	bool need_rotate = false, should_age = false, retried = false;
 	int swappiness = get_swappiness(lruvec, sc);
 	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
 	/*
-	 * Avoid aging the lagging type under extreme swappiness,
-	 * as reclaim of the unpreferred type is disabled until
-	 * DEF_PRIORITY / 2.
+	 * Avoid aging the lagging, unpreferred type at extreme swappiness;
+	 * rely on imbalanced aging once priority reaches AGING_PRIORITY
 	 */
-	bool allow_no_populatable_age = (sc->priority <= DEF_PRIORITY / 2) ||
-		(swappiness > MIN_SWAPPINESS + 1 && swappiness < MAX_SWAPPINESS);
+	bool allow_drained_age = sc->priority < DEF_PRIORITY &&
+		swappiness > MIN_SWAPPINESS + 1 && swappiness < MAX_SWAPPINESS;
 
 	/*
 	 * Proactive reclaim initiated by userspace for anonymous memory only.
@@ -5248,9 +5243,9 @@ static bool try_to_shrink_lruvec(struct
 	lru_gen_prepare_scan(lruvec, sc, memcg, swappiness, nr);
 
 	while (nr[LRU_GEN_ANON] > 0 || nr[LRU_GEN_FILE] > 0) {
-		unsigned long orig_file, orig_anon;
-		unsigned long max_seq;
-		bool no_populatable;
+		bool drained = false, do_drained_age;
+		DEFINE_MAX_SEQ(lruvec);
+		unsigned long new_seq;
 		long scanned;
 
 		if (mem_cgroup_below_min(sc->target_mem_cgroup, memcg)) {
@@ -5258,9 +5253,7 @@ static bool try_to_shrink_lruvec(struct
 			break;
 		}
 
-		orig_anon = nr[LRU_GEN_ANON];
-		orig_file = nr[LRU_GEN_FILE];
-		scanned = evict_folio_lists(nr, lruvec, sc, swappiness, MIN_LRU_BATCH);
+		scanned = evict_folio_lists(nr, lruvec, sc, swappiness, MIN_LRU_BATCH, &drained);
 
 		if (should_abort_scan(lruvec, sc))
 			break;
@@ -5272,20 +5265,26 @@ static bool try_to_shrink_lruvec(struct
 		if (root_reclaim(sc) && should_age)
 			break;
 
-		/* Limit no_populatable aging to once per try_to_shrink_lruvec() */
-		no_populatable = allow_no_populatable_age &&
-			((nr[LRU_GEN_ANON] && nr[LRU_GEN_ANON] == orig_anon) ||
-			(nr[LRU_GEN_FILE] && nr[LRU_GEN_FILE] == orig_file));
-		allow_no_populatable_age &= !no_populatable;
-		max_seq = READ_ONCE((lruvec)->lrugen.max_seq);
-		if (should_run_aging(lruvec, max_seq, sc, swappiness, no_populatable)) {
+		/*
+		 * Allow drained aging only once per invocation and skip it if
+		 * max_seq advanced concurrently during eviction
+		 */
+		new_seq = READ_ONCE(lruvec->lrugen.max_seq);
+		do_drained_age = drained && allow_drained_age && max_seq == new_seq;
+		allow_drained_age &= !do_drained_age;
+		if (do_drained_age || should_run_aging(lruvec, new_seq, sc, swappiness)) {
 			trace_mm_mglru_aging(lruvec, sc, swappiness);
-			if (try_to_inc_max_seq(lruvec, max_seq, swappiness, false))
+			if (try_to_inc_max_seq(lruvec, new_seq, swappiness, false))
 				need_rotate = true;
 			should_age = true;
 		}
-		if (!scanned)
-			break;
+
+		if (!scanned) {
+			/* Retry once if aging advanced max_seq */
+			if (retried || max_seq == READ_ONCE(lruvec->lrugen.max_seq))
+				break;
+			retried = true;
+		}
 
 		cond_resched();
 	}
@@ -5332,7 +5331,7 @@ static int shrink_one(struct lruvec *lru
 	if (need_rotate)
 		return MEMCG_LRU_YOUNG;
 
-	if (lruvec_is_sizable(lruvec, sc))
+	if (lruvec_is_sizable(lruvec, sc, sc->reclaim_idx + 1))
 		return 0;
 
 	/* one retry if too small */
@@ -5915,11 +5914,11 @@ static int run_eviction(struct lruvec *l
 		if (sc->nr_reclaimed >= nr_to_reclaim)
 			return 0;
 
-		lruvec_evictable_size(lruvec, sc, swappiness, nr_to_scan);
+		lruvec_evictable_size(lruvec, sc, swappiness, nr_to_scan, sc->reclaim_idx + 1);
 		lru_gen_balance_scan(lruvec, sc, swappiness,
 				     nr_to_reclaim - sc->nr_reclaimed, nr_to_scan);
 
-		if (!evict_folio_lists(nr_to_scan, lruvec, sc, swappiness, MAX_LRU_BATCH))
+		if (!evict_folio_lists(nr_to_scan, lruvec, sc, swappiness, MAX_LRU_BATCH, NULL))
 			return 0;
 
 		cond_resched();
--- a/mm/vmscan.h~b
+++ a/mm/vmscan.h
@@ -117,7 +117,7 @@ struct scan_control {
 };
 
 #ifdef CONFIG_LRU_GEN
-/* The number of pages of @type in the generation @seq, summed over all zones */
+/* Number of pages of @type in generation @seq below @max_zone */
 static inline unsigned long lruvec_gen_size(struct lru_gen_folio *lrugen,
 					    int type, unsigned long seq,
 					    int max_zone)
_


      parent reply	other threads:[~2026-11-14 22:09 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-14 20:07 Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 01/16] mm/mglru: report evictable sizes per type Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 02/16] mm/mglru: extract lruvec_populated_min_seq() helper Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 03/16] mm/mglru: reclaim from the oldest populated gen Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 04/16] mm/mglru: lift the forced protection of reclaimer Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 05/16] mm/mglru: skip zones above reclaim_idx instead of rotating them Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 06/16] mm/mglru: return for unavailable anon reclaim earlier Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 07/16] mm/mglru: balance anon and file scan budget by refault rate Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 08/16] mm/mglru: lift youngest gen protection for higher priority Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 09/16] mm/mglru: Avoid placing rejected folios below populated gen Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 10/16] mm/mglru: Run aging when pages are rare outside the youngest gen Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 11/16] mm/mglru: Run aging when one type is drained Barry Song (Xiaomi)
2026-11-14 20:07 ` [PATCH v9 12/16] mm/mglru: Restrict eviction accounting to eligible zones Barry Song (Xiaomi)
2026-11-14 20:08 ` [PATCH v9 13/16] mm/mglru: Reduce LRU lock contention by shrinking folio batches in inc_min_seq Barry Song (Xiaomi)
2026-11-14 20:08 ` [PATCH v9 14/16] mm/mglru: Avoid prolonged anon reclaim with swappiness=200 Barry Song (Xiaomi)
2026-11-14 20:08 ` [PATCH v9 15/16] mm/mglru: Make swappiness=1 file reclaim more aggressive Barry Song (Xiaomi)
2026-11-14 20:08 ` [PATCH v9 16/16] mm/mglru: Avoid scan anon while swapcache is low for GFP_NOIO allocation Barry Song (Xiaomi)
2026-11-14 22:09 ` Andrew Morton [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261114140949.c47f9cf9c1ea22315092d880@linux-foundation.org \
    --to=akpm@linux-foundation.org \
    --cc=axelrasmussen@google.com \
    --cc=baohua@kernel.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=baoquan.he@linux.dev \
    --cc=hannes@cmpxchg.org \
    --cc=hui.zhu@linux.dev \
    --cc=kasong@tencent.com \
    --cc=kunwu.chan@gmail.com \
    --cc=liam@infradead.org \
    --cc=lianux.mm@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=liuxiang.277@bytedance.com \
    --cc=lyugaofei@xiaomi.com \
    --cc=mhocko@kernel.org \
    --cc=mhocko@suse.com \
    --cc=qi.zheng@linux.dev \
    --cc=ridong.chen@linux.dev \
    --cc=rppt@kernel.org \
    --cc=ryncsn@gmail.com \
    --cc=shakeel.butt@linux.dev \
    --cc=sidchintamaneni@gmail.com \
    --cc=surenb@google.com \
    --cc=wangzicheng@honor.com \
    --cc=weixugc@google.com \
    --cc=yuanchu@google.com \
    --cc=zhangbo56@xiaomi.com \
    --cc=zhuhui@kylinos.cn \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®