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

From: Kairui Song

Date: Sun Sep 06 2026 - 01:52:15 EST


On Sun, Sep 6, 2026 at 12:56 PM Barry Song <baohua@xxxxxxxxxx> wrote:
>
> On Sun, Sep 6, 2026 at 9:18 AM 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.
> >
> > 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.
> >
> > On Android this is triggered by dm-verity hash-block reads through
> > dm-bufio, which use GFP_NOIO:
> >
> > verity_verify_io -> verity_hash_for_block -> verity_verify_level
> > -> dm_bufio_read_with_ioprio -> new_read -> __bufio_new
> > -> alloc_buffer
> > gfp: GFP_NOIO | __GFP_NORETRY | __GFP_NOMEMALLOC | __GFP_NOWARN
> >
> > Such a reclaimer can land on a memcg with a large, unswapped anon LRU and
> > a tiny file LRU (e.g. inactive_anon ~335 MB vs inactive_file ~4 MB, with
> > negligible swapcache). shrink_lruvec() then keeps feeding that huge anon
> > list into shrink_folio_list() - ~2400 shrink_folio_list() calls, ~93,000
> > anon folios scanned - where every folio is kept because it needs IO. The
> > 150+ ms above is one such single shrink_lruvec() pass (not accumulated
> > across a reclaim cycle), and it reclaims nothing; the actual progress
> > comes entirely from the file side.
> >
> > Aging anon alongside file does have some value for a later __GFP_IO
> > reclaimer, so it is not strictly pure overhead. But that aging is only
> > deferred, not lost: kswapd and other __GFP_IO reclaimers still walk and
> > age anon. Spending ~168 ms aging memory that this context cannot reclaim
> > is not a worthwhile trade-off in a latency-sensitive path.
> >
> > To stay conservative, this only skips anon when the swapcache is really
> > tiny - below 1/64 of the anon LRU - i.e. when essentially no anon on the
> > list can be reclaimed without IO. Whenever there is a meaningful amount of
> > swapcached anon, the normal path is used and anon is scanned and aged as
> > before.
>
> I notice this only fixes the active/inactive LRU case. To address the
> MGLRU case, it seems we may need a more fundamental change.

It won't be too hard if we just calculate the type and number to scan
upfront, and I believe this is a similar issue due to the same root
cause of the OOM and swappiness issue of MGLRU, which I mentiones
before (see point 4, "force protection of the youngest two gens"):
https://lore.kernel.org/linux-mm/CAMgjq7BoekNjg-Ra3C8M7=8=75su38w=HD782T5E_cxyeCeH_g@xxxxxxxxxxxxxx/

Removing that force protection and calculate the number to scan
upfront, then we can also make use of can_reclaim_anon_pages, shift
all scan budget to file type. Aging won't be triggered at default
priority, resulting in zero overhead. We can then either offload aging
to a worker or defer it if aging isn't helpful for one reclaim cycle.