Re: [RFC PATCH v2 1/5] mm: mglru: avoid scanning empty generations in scan_folios()
From: Barry Song
Date: Mon Jul 27 2026 - 19:19:11 EST
On Sun, Jul 26, 2026 at 8:21 PM Barry Song (Xiaomi) <baohua@xxxxxxxxxx> wrote:
>
> Commit 16b475d2ac3c ("mm/mglru: avoid reclaim type fall back when
> isolation makes no progress") only falls back to the other type when
> scanned == 0. However, I have frequently observed cases where
> scanned > 0, but the older reclaimable generation becomes empty
> after the first scan_folios(). As a result, the second
> scan_folios() for the same type performs a redundant scan over an
> empty generation.
>
> We can avoid this by checking whether the reclaimable generation has
> become empty when scanned < nr_to_scan and we still have fewer than
> MIN_LRU_BATCH isolated folios after scan_folios().
>
> Signed-off-by: Barry Song (Xiaomi) <baohua@xxxxxxxxxx>
> ---
> mm/vmscan.c | 11 +++++++----
> 1 file changed, 7 insertions(+), 4 deletions(-)
>
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 566c4e837c7d..babbce4bbfe8 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -4852,11 +4852,14 @@ static int isolate_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
> break;
> }
> /*
> - * If scanned > 0 and isolated == 0, avoid falling back to the
> - * other type, as this type remains sufficient. Falling back
> - * too readily can disrupt the positive_ctrl_err() bias.
> + * If scanned >= nr_to_scan or isolated >= MIN_LRU_BATCH,
> + * avoid falling back to the other type. The preferred
> + * type is still reclaimable; otherwise, it would have
> + * already run out of reclaimable generations. Falling
> + * back too readily can disrupt the positive_ctrl_err()
> + * bias.
> */
> - if (!scanned)
> + if (scanned < nr_to_scan && *isolated < MIN_LRU_BATCH)
https://sashiko.dev/#/patchset/20260726122123.7614-1-baohua%40kernel.org
"
> - if (!scanned)
> + if (scanned < nr_to_scan && *isolated < MIN_LRU_BATCH)
> type = !type;
> }
Does the check for *isolated < MIN_LRU_BATCH have any effect here?
Looking earlier in isolate_folios(), there is an if (*isolated) check that
breaks out of the loop:
if (*isolated)
break;
Since execution only reaches this point if *isolated is exactly 0, isn't
*isolated < MIN_LRU_BATCH unconditionally true?
Additionally, will this logic work correctly when large folios are involved?
If scan_folios() encounters an un-isolatable large folio (for example, an
order-9 THP with 512 pages), the scanned variable is incremented by the base
page count (512).
Since nr_to_scan is capped at MIN_LRU_BATCH (64) by the caller evict_folios(),
scanned < nr_to_scan (512 < 64) will evaluate to false even if the generation
is completely empty.
Does this prevent the fallback and inadvertently defeat the optimization on
systems utilizing large folios?
"
Right. What I actually need is a way to tell when scan_folios()
returns after the current generation's LRU lists are empty, but I
haven't found a good approach. Checking scanned < nr_to_scan works
well for small folios, but not for large folios.
Maybe we could add an argument to scan_folios(), like this:
@@ -4707,7 +4711,8 @@ static bool isolate_folio(struct lruvec *lruvec,
struct folio *folio, struct sca
static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
struct scan_control *sc, int type, int tier,
- struct list_head *list, int *isolatedp)
+ struct list_head *list, int *isolatedp,
+ bool *exhausted)
{
int i;
int gen;
@@ -4776,6 +4781,11 @@ static int scan_folios(unsigned long
nr_to_scan, struct lruvec *lruvec,
scanned, skipped, isolated,
type ? LRU_INACTIVE_FILE : LRU_INACTIVE_ANON);
+ /*
+ * This scan exhausted the current generation before reaching the
+ * scan target or accumulating enough isolated folios.
+ */
+ *exhausted = remaining > 0 && isolated < MIN_LRU_BATCH;
*isolatedp = isolated;
return scanned;
}
Then another question comes up: suppose we have four generations, and
scan_folios() exhausts the oldest one while the second-oldest still
contains reclaimable folios. The current implementation doesn't move
on to scan the second-oldest generation. Instead, it returns,
leaving that generation with no chance to be scanned at the current
sc->priority. That doesn't seem right. Let me investigate this as
well.
Thanks
Barry