Re: [RFC PATCH] mm: vmscan: avoid anon scanning for GFP_NOIO with low swapcache in MGLRU

From: Barry Song

Date: Mon Oct 12 2026 - 07:03:29 EST


On Mon, Oct 12, 2026 at 4:26 PM Xiang Liu <liuxiang.277@xxxxxxxxxxxxx> wrote:
>
> Hi Barry,
>
> Thanks for reviewing.
>
> > I'm not a fan of this fix, as setting
> > scan_swappiness = MIN_SWAPPINESS will force a cold/hot
> > inversion for anonymous folios.
>
> The patch description outlines the tradeoff:
>
> > The tradeoff is synchronous aging. With four anon generations and two
> > file generations, advancing max_seq can require moving the oldest anon
> > folios to the next generation. This patch accepts that cost to avoid
> > low-yield anonymous scanning. It keeps the original swappiness in
> > try_to_inc_max_seq(), avoiding additional use of the inc_min_seq()
> > single-type shortcut that advances min_seq without processing the
> > oldest folios and can cause cold/hot inversions.
>
> To evaluate this tradeoff, we varied the oldest anon population and
> compared synchronous aging with baseline evict_folios() time under
> GFP_NOIO with very little swapcache. In the cases reported in the patch,
> aging was substantially cheaper than low-yield anon eviction, and
> allocation and reclaim times decreased. This motivated retaining the
> synchronous cost while avoiding additional use of the existing
> cold/hot-inversion shortcut.
>
> The diff uses scan_swappiness to decide whether aging is needed, and
> passes the original swappiness to try_to_inc_max_seq():
>
> if (should_run_aging(lruvec, max_seq, sc, scan_swappiness)) {
> if (try_to_inc_max_seq(lruvec, max_seq, swappiness, false))
> need_rotate = true;
> should_age = true;
> }
>
> inc_min_seq() receives the original swappiness through inc_max_seq().
> With a nonzero original value, the anon shortcut is bypassed. When anon
> has four generations, its oldest folios are processed before max_seq
> advances, retaining the synchronous cost described above.
>
> Am I missing another path where this change would trigger the shortcut?

Interesting. I missed that eviction uses the modified swappiness
while aging still uses the original swappiness. IMHO, this seems
to work, but we need a big comment here to explain the rationale.

Also, it feels really strange that we have to hack two places:
one in `lruvec_is_sizable()` and the other in
`try_to_shrink_lruvec()`.

so we could make it a helper? BTW, we have already handled
return 0 in

static long get_nr_to_scan(struct lruvec *lruvec, struct scan_control *sc,
struct mem_cgroup *memcg, int swappiness)
{
unsigned long nr_to_scan, evictable;
struct pglist_data *pgdat = lruvec_pgdat(lruvec);

/*
* Proactive reclaim initiated by userspace for anonymous memory only.
* SWAPPINESS_ANON_ONLY is set only on the proactive reclaim path, so
* warn if it shows up elsewhere. When anon cannot be reclaimed (e.g.
* no swap), return 0 to skip the scan entirely, avoiding useless scan
* work when there is nothing eligible to reclaim.
*/
if (swappiness == SWAPPINESS_ANON_ONLY) {
WARN_ON_ONCE(!sc->proactive);
if (!can_reclaim_anon_pages(memcg, pgdat->node_id, sc))
return 0;
}
...
}

do we really need to handle it again in lruvec_is_sizable()?

Kairui has a patch which hasn't been sent out yet:

mm/mglru: reclaim from the oldest populated gen

https://git.kernel.org/pub/scm/linux/kernel/git/baohua/linux.git/commit/?id=b15a053bc

which would allow shrinking MIN_NR_GENS generations and better support
the GFP_NOIO case.

For now, let's clean up the code, add comments, and introduce a helper
for your approach so that we can see if it can be made easier to follow
by a RFC v2.

Best Regards
Barry