Re: [RFC PATCH v5 03/14] mm/mglru: reclaim from the oldest populated gen
From: KunWu Chan
Date: Tue Oct 27 2026 - 05:58:29 EST
On Fri, Oct 23, 2026 at 6:02 AM Barry Song (Xiaomi) <baohua@xxxxxxxxxx> wrote:
>
> From: Kairui Song <kasong@xxxxxxxxxxx>
>
> Currently MGLRU tries to keep the gen numbers of the two types in sync,
> avoiding reducing the gen number of one type when the other type has
> more gens, as described in the comment in try_to_inc_min_seq().
>
> This makes scan_folios() stuck reclaiming the type with fewer gens: it
> always scanned the oldest gen of the type, even when that gen was
> empty, so reclaim made no progress on that type unless forced aging
> created a new gen and eliminated the dead empty gen.
>
> There are other approaches, like lifting the gen number
> synchronization [1]. However, that would break the gen layout used by
> lru_gen_folio_seq(): newly added folios would no longer have a
> well-defined hotness according to their gen number, which reduces
> accuracy. It is also not pressure-aware, since with gentle reclaim we
> still want to respect the gen layout (hotness).
>
> Instead, reclaim from whichever populated oldest gen is left, but only
> when the default reclaim priority can't satisfy the reclaimer's
> requirement. This is typical for swappiness limitations: e.g., low
> swappiness asks the reclaimer to reclaim from file, but file gens are
> drained, so the reclaim cycle fails and priority is raised.
>
> At the default priority the scan quota is 1/4096 of the evictable
> size, and the old behavior is kept there to respect the hotness
> distribution. Once priority is raised, the quota is at least 1/2048,
> so the hotness bias introduced by reclaiming from a younger gen is
> bounded by that quota, which seems fair. Global reclaim for MGLRU may
> start with DEF_PRIORITY / 2 (1/64); the ratio is still reasonable at
> that point, and when global pressure is more critical, we should
> reclaim harder.
>
> Note that scan_folios() still refuses to reclaim a type left with only
> MIN_NR_GENS gens, so a completely drained type still requires aging.
> This change only avoids scanning empty gens when populated reclaimable
> gens remain.
>
> Link: https://lore.kernel.org/linux-mm/20260726122123.7614-4-baohua@xxxxxxxxxx/ [1]
> Signed-off-by: Kairui Song <kasong@xxxxxxxxxxx>
> Signed-off-by: Barry Song <baohua@xxxxxxxxxx>
> ---
> mm/vmscan.c | 30 +++++++++++++++++++-----------
> 1 file changed, 19 insertions(+), 11 deletions(-)
>
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 500ef23949fd..d7eb15533de1 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -3451,11 +3451,9 @@ static int __folio_inc_gen(struct folio *folio, int old_gen, bool *increased)
> }
>
> /* protect pages accessed multiple times through file descriptors */
> -static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio)
> +static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio, int old_gen)
> {
> - int type = folio_is_file_lru(folio);
> - struct lru_gen_folio *lrugen = &lruvec->lrugen;
> - int new_gen, old_gen = lru_gen_from_seq(lrugen->min_seq[type]);
> + int new_gen;
> bool gen_increased;
>
> new_gen = __folio_inc_gen(folio, old_gen, &gen_increased);
> @@ -4836,7 +4834,7 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent,
> ******************************************************************************/
>
> static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_control *sc,
> - int tier_idx)
> + int min_gen, int tier_idx)
> {
> int gen = folio_lru_gen(folio);
> int type = folio_is_file_lru(folio);
> @@ -4854,14 +4852,14 @@ static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_c
> return false;
>
> /* promoted */
> - if (gen != lru_gen_from_seq(lrugen->min_seq[type])) {
> + if (gen != min_gen) {
> list_move(&folio->lru, &lrugen->folios[gen][type][zone]);
> return true;
> }
>
> /* protected */
> if (tier > tier_idx || refs + workingset == BIT(LRU_REFS_WIDTH) + 1) {
> - gen = folio_inc_gen(lruvec, folio);
> + gen = folio_inc_gen(lruvec, folio, min_gen);
> list_move(&folio->lru, &lrugen->folios[gen][type][zone]);
>
> /* don't count the workingset being lazily promoted */
> @@ -4876,7 +4874,7 @@ static bool sort_folio(struct lruvec *lruvec, struct folio *folio, struct scan_c
>
> /* ineligible */
> if (zone > sc->reclaim_idx) {
> - gen = folio_inc_gen(lruvec, folio);
> + gen = folio_inc_gen(lruvec, folio, min_gen);
> list_move_tail(&folio->lru, &lrugen->folios[gen][type][zone]);
> return true;
> }
> @@ -4919,17 +4917,27 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
> int scanned = 0;
> int isolated = 0;
> int skipped = 0;
> + unsigned long min_seq;
> unsigned long remaining = nr_to_scan;
> struct lru_gen_folio *lrugen = &lruvec->lrugen;
>
> VM_WARN_ON_ONCE(nr_to_scan > MAX_LRU_BATCH);
> VM_WARN_ON_ONCE(!list_empty(list));
>
> + /*
> + * Respect the gen hotness distribution at low priority to avoid
> + * aging and scan cost, but prefer to satisfy the reclaimers request
> + * when under pressure to respect swappiness more and reduce IO cost.
> + */
> + if (sc->priority == DEF_PRIORITY)
> + min_seq = lrugen->min_seq[type];
> + else
> + min_seq = lruvec_populated_min_seq(lruvec, type, lrugen->max_seq - MIN_NR_GENS + 1);
> +
> if (get_nr_gens(lruvec, type) == MIN_NR_GENS)
> return 0;
>
> - gen = lru_gen_from_seq(lrugen->min_seq[type]);
> -
> + gen = lru_gen_from_seq(min_seq);
> for (i = MAX_NR_ZONES; i > 0; i--) {
> LIST_HEAD(moved);
> int skipped_zone = 0;
> @@ -4947,7 +4955,7 @@ static int scan_folios(unsigned long nr_to_scan, struct lruvec *lruvec,
>
> scanned += delta;
>
> - if (sort_folio(lruvec, folio, sc, tier))
> + if (sort_folio(lruvec, folio, sc, gen, tier))
> sorted += delta;
> else if (isolate_folio(lruvec, folio, sc)) {
> list_add(&folio->lru, list);
> --
> 2.34.1
>
At raised priority, scanning from the oldest populated gen avoids
wasted work on empty generations. The change to pass min_gen through
sort_folio() and folio_inc_gen() keeps promotion semantics correct
when the scanned gen is above min_seq[type].
Reviewed-by: Kunwu Chan <kunwu.chan@xxxxxxxxx>