[RFC PATCH] mm: vmscan: avoid anon scanning for GFP_NOIO with low swapcache
From: Bo Zhang
Date: Thu Sep 03 2026 - 00:02:10 EST
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.
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);
+ unsigned long anon_pages, swapcache;
+
+ if (!sc || (sc->gfp_mask & __GFP_IO))
+ return false;
+
+ anon_pages = lruvec_page_state(lruvec, NR_INACTIVE_ANON) +
+ lruvec_page_state(lruvec, NR_ACTIVE_ANON);
+ swapcache = lruvec_page_state(lruvec, NR_SWAPCACHE);
+
+ return swapcache < min(anon_pages >> 6, SWAP_CLUSTER_MAX);
+}
+
static inline bool can_reclaim_anon_pages(struct mem_cgroup *memcg,
int nid,
struct scan_control *sc)
@@ -348,11 +384,13 @@ static inline bool can_reclaim_anon_pages(struct mem_cgroup *memcg,
* For non-memcg reclaim, is there
* space in any swap device?
*/
- if (get_nr_swap_pages() > 0)
+ if (get_nr_swap_pages() > 0 &&
+ !reclaimable_anon_is_low(memcg, nid, sc))
return true;
} else {
/* Is the memcg below its swap limit? */
- if (mem_cgroup_get_nr_swap_pages(memcg) > 0)
+ if (mem_cgroup_get_nr_swap_pages(memcg) > 0 &&
+ !reclaimable_anon_is_low(memcg, nid, sc))
return true;
}
@@ -2674,26 +2712,6 @@ static bool should_clear_pmd_young(void)
#define get_memcg_gen(seq) ((seq) % MEMCG_NR_GENS)
#define get_memcg_bin(bin) ((bin) % MEMCG_NR_BINS)
-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 int get_swappiness(struct lruvec *lruvec, struct scan_control *sc)
{
struct mem_cgroup *memcg = lruvec_memcg(lruvec);
--
2.34.1