diff options
| author | Bo Zhang <zhangbo56@xiaomi.com> | 2026-09-08 14:26:49 +0800 |
|---|---|---|
| committer | Andrew Morton <akpm@linux-foundation.org> | 2026-10-01 19:12:25 -0700 |
| commit | b2ccead9cdae1d0559acb2b9b29292568a2bb4ca (patch) | |
| tree | 584f6e5f350573060bb110673694888163f96549 /include/linux | |
| parent | f898f5559c093885e15609e1855e5a5486f54266 (diff) | |
| download | linux-next-b2ccead9cdae1d0559acb2b9b29292568a2bb4ca.tar.gz linux-next-b2ccead9cdae1d0559acb2b9b29292568a2bb4ca.zip | |
mm: vmscan: avoid anon scanning for GFP_NOIO with low swapcache
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 legitimately use GFP_NOIO because they run underneath the
IO path:
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.
Note this only addresses the traditional active/inactive LRU. MGLRU
selects anon vs file scanning in its own path and is not covered here;
fixing the MGLRU case is left as a TODO.
Link: https://lore.kernel.org/20260908062649.1045883-1-zhangbo56@xiaomi.com
Signed-off-by: Bo Zhang <zhangbo56@xiaomi.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Barry Song <baohua@kernel.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Kairui Song <kasong@tencent.com>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Shakeel Butt <shakeel.butt@linux.dev>
Diffstat (limited to 'include/linux')
0 files changed, 0 insertions, 0 deletions
