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

From: Barry Song

Date: Thu Sep 03 2026 - 06:44:45 EST


On Thu, Sep 3, 2026 at 12:02 PM Bo Zhang <zhangbo0325@xxxxxxxxx> wrote:
>
> We have observed some cases where memory is allocated with GFP_NOIO, so
> we cannot reclaim any anon folios unless they are in swapcache. We can
> end up spending more than 150 ms looping in `shrink_folio_list()` scanning
> non-swapcache folios without reclaiming a single folio. This is pure
> overhead.
>

Yes. The coexistence of `GFP_NOIO` and anon rmap scanning seems
nasty. We might also want to do something like the following, but
the side effect is that it might keep a folio while preserving its
PTE young state, indirectly making the folio semantically hotter.
So this may not be a good option. Skipping anon scanning in the first
place seems more sensible.

diff --git a/mm/vmscan.c b/mm/vmscan.c
index ba7adf36e69f..f15aa3573030 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -1248,6 +1248,19 @@ static unsigned int shrink_folio_list(struct
list_head *folio_list,
}
}

+ /*
+ * Don't waste time doing rmap and scanning PTE access for
+ * non-reclaimable folios.
+ */
+ if (!do_demote_pass && folio_test_anon(folio) &&
+ folio_test_swapbacked(folio) &&
+ !folio_test_swapcache(folio)) {
+ if (!(sc->gfp_mask & __GFP_IO))
+ goto keep_locked;
+ if (folio_maybe_dma_pinned(folio))
+ goto keep_locked;
+ }
+
if (!ignore_references)
references = folio_check_references(folio, sc);

@@ -1281,10 +1294,6 @@ static unsigned int shrink_folio_list(struct
list_head *folio_list,
!folio_test_swapcache(folio)) {
int ret;

- if (!(sc->gfp_mask & __GFP_IO))
- goto keep_locked;
- if (folio_maybe_dma_pinned(folio))
- goto keep_locked;
if (folio_test_large(folio)) {
/* cannot split folio, skip it */
if (folio_expected_ref_count(folio) !=

> This is particularly true on systems using zRAM, where swapcache is
> relatively rare. So let's check whether anon reclaim is allowed by
> GFP_IO and whether there is enough swapcache to make it worthwhile. If
> the swapcache is extremely low, we're essentially searching for a
> needle in a haystack, so let's avoid scanning anon in the first place.
>
> Signed-off-by: Bo Zhang <zhangbo56@xxxxxxxxxx>
> ---
> mm/vmscan.c | 62 ++++++++++++++++++++++++++++++++++-------------------
> 1 file changed, 40 insertions(+), 22 deletions(-)
>
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 56708d1d2dfd..192bd0980121 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -339,6 +339,42 @@ static bool can_demote(int nid, struct scan_control *sc,
> return !nodes_empty(allowed_mask);
> }
>
> +static struct lruvec *get_lruvec(struct mem_cgroup *memcg, int nid)
> +{
> + struct pglist_data *pgdat = NODE_DATA(nid);
> +
> +#ifdef CONFIG_MEMCG
> + if (memcg) {
> + struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec;
> +
> + /* see the comment in mem_cgroup_lruvec() */
> + if (!lruvec->pgdat)
> + lruvec->pgdat = pgdat;
> +
> + return lruvec;
> + }
> +#endif
> + VM_WARN_ON_ONCE(!mem_cgroup_disabled());
> +
> + return &pgdat->__lruvec;
> +}
> +
> +static inline bool reclaimable_anon_is_low(struct mem_cgroup *memcg,
> + int nid, struct scan_control *sc)
> +{
> + struct lruvec *lruvec = get_lruvec(memcg, nid);

https://sashiko.dev/#/patchset/20260903040131.4016290-1-zhangbo56%40xiaomi.com
sashiko says:

"Will this cause a kernel panic during global reclaim when memory cgroups are
enabled?
When can_reclaim_anon_pages() is called for global reclaim, memcg is NULL.
Since get_lruvec() is called unconditionally with this NULL memcg, it will
trigger the VM_WARN_ON_ONCE(!mem_cgroup_disabled()) and return the raw
node lruvec (&pgdat->__lruvec).
When lruvec_page_state() is then called with this non-memcg lruvec:
mm/memcontrol.c:lruvec_page_state() {
...
pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec);
x = READ_ONCE(pn->lruvec_stats->state[i]);
...
}
Because the lruvec is actually embedded in pglist_data rather than
mem_cgroup_per_node, won't container_of() produce a wild pointer, leading
to a fatal out-of-bounds memory access when trying to read the stats?"

it seems we could just use mem_cgroup_lruvec() instead:

static inline bool reclaimable_anon_is_low(struct mem_cgroup *memcg,
int nid, struct scan_control *sc)
{
- struct lruvec *lruvec = get_lruvec(memcg, nid);
+ struct lruvec *lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid));
unsigned long anon_pages, swapcache;

if (!sc || (sc->gfp_mask & __GFP_IO))

I applied your patch on my PC with the above change. I didn't see any
panic or run into any problems.

Best Regards
Barry