[PATCH 3/4] mm/mglru: add debugfs knob to control cross-node empty walk skip threshold
Baoquan He <[email protected]>
| Newsgroups | org.kvack.linux-mm |
|---|---|
| Message-ID | <[email protected]> |
Convert the hardcoded MGLRU_EMPTY_SKIP_GENS from a #define to a runtime-tunable variable (mglru_empty_skip_gens, default 4), and add a debugfs command to control it: echo "skip_empty <N>" > /sys/kernel/debug/lru_gen (0 = disable, default 4) echo "skip_empty" > /sys/kernel/debug/lru_gen (show current value) Setting N=0 disables the cross-node empty walk suppression entirely, allowing easy A/B testing without recompiling the kernel. Default 4 matches MAX_NR_GENS so the skip window covers at most one full LRU generation slide. This is intended as a development/tuning aid for RFC. Whether it should graduate to a permanent ABI (sysctl) can be decided once field data demonstrates the optimization is worth keeping. Signed-off-by: Baoquan He <[email protected]> --- mm/vmscan.c | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/mm/vmscan.c b/mm/vmscan.c index c39b392d7b7e..631bcd89b196 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -2709,14 +2709,6 @@ static bool should_clear_pmd_young(void) return arch_has_hw_nonleaf_pmd_young() && get_cap(LRU_GEN_NONLEAF_YOUNG); } -/* - * Cross-node empty walk suppression. lru_gen_use_mm() marks an mm used on all - * nodes, so aging on a node where the mm has no memory wastes a full page table - * walk. Skip such an mm for up to MGLRU_EMPTY_SKIP_GENS generations after an - * empty walk, then force-rescan to close migration/mlock/NUMA-balancing windows. - */ -#define MGLRU_EMPTY_SKIP_GENS 4 - /****************************************************************************** * shorthand helpers ******************************************************************************/ @@ -2927,6 +2919,9 @@ static struct lru_gen_mm_state *get_mm_state(struct lruvec *lruvec) return &lruvec->mm_state; } +/* tunable empty-walk skip threshold; defined later, get_next_mm() needs it */ +static unsigned long mglru_empty_skip_gens; + static struct mm_struct *get_next_mm(struct lru_gen_mm_walk *walk) { int key; @@ -2951,7 +2946,7 @@ static struct mm_struct *get_next_mm(struct lru_gen_mm_walk *walk) DEFINE_MAX_SEQ(walk->lruvec); unsigned long empty_seq = READ_ONCE(mm->lru_gen.empty_map_seq); - if (max_seq < empty_seq + MGLRU_EMPTY_SKIP_GENS) + if (max_seq < empty_seq + READ_ONCE(mglru_empty_skip_gens)) return NULL; /* skip: < K gens since empty */ /* K generations passed → force rescan */ @@ -4273,6 +4268,15 @@ static bool lruvec_is_reclaimable(struct lruvec *lruvec, struct scan_control *sc /* to protect the working set of the last N jiffies */ static unsigned long lru_gen_min_ttl __read_mostly; +/* + * Cross-node empty-walk skip threshold: skip an mm on node N for up to + * @mglru_empty_skip_gens generations after an empty walk, then force-rescan + * (closes migration/mlock/NUMA-balancing windows). Default 4 matches + * MAX_NR_GENS; 0 disables the suppression. Tunable via: + * echo "skip_empty <N>" > /sys/kernel/debug/lru_gen + */ +static unsigned long mglru_empty_skip_gens __read_mostly = 4; + static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc) { struct mem_cgroup *memcg; @@ -5870,6 +5874,25 @@ static ssize_t lru_gen_seq_write(struct file *file, const char __user *src, if (!*cur) continue; + /* + * set/show the empty-walk skip threshold: "skip_empty <N>" + */ + if (!strncmp(cur, "skip_empty", 10)) { + cur += 10; + cur = skip_spaces(cur); + if (*cur) { + unsigned long val; + + if (sscanf(cur, "%lu", &val) == 1) + WRITE_ONCE(mglru_empty_skip_gens, val); + } else { + pr_info("MGLRU empty skip threshold: %lu generations (0=disabled)\n", + READ_ONCE(mglru_empty_skip_gens)); + } + err = 0; + continue; + } + n = sscanf(cur, "%c %llu %u %lu %n %4s %n %lu %n", &cmd, &memcg_id, &nid, &seq, &end, swap_string, &end, &opt, &end); if (n < 4 || cur[end]) { -- 2.54.0