Skip to contentSkip to conversation

Avoid building lrugen page table walk code

PATCH In review Aneesh Kumar K.V 2 participants 9 messages Jul 6, 2023 – Jul 8, 2023

Subsystems (9)
Memory management x86 MM core arm64 +5 more subsystems
lore
Share & tools
lore
#0 lore raw

v2 cover Avoid building lrugen page table walk code

This patchset avoids building changes added by commit bd74fdaea146 ("mm:
multi-gen LRU: support page table walks") on platforms that don't support hardware atomic updates of access bits.

Aneesh Kumar K.V (5):
mm/mglru: Create a new helper iterate_mm_list_walk mm/mglru: Move Bloom filter code around
mm/mglru: Move code around to make future patch easy mm/mglru: move iterate_mm_list_walk Helper mm/mglru: Don't build multi-gen LRU page table walk code on
architecture not supported

 arch/Kconfig               |   3 +
 arch/arm64/Kconfig         |   1 +
 arch/x86/Kconfig           |   1 +
 include/linux/memcontrol.h |   2 +-
 include/linux/mm_types.h   |  10 +-
 include/linux/mmzone.h     |  12 +-
 kernel/fork.c              |   2 +-
 mm/memcontrol.c            |   2 +-
 mm/vmscan.c                | 955 +++++++++++++++++++------------------
 9 files changed, 528 insertions(+), 460 deletions(-)
Signature · 1 line
2.41.0
#1 lore raw

PATCH v2 1/5 mm/mglru: Create a new helper iterate_mm_list_walk

In a later patch we will not build this on ppc64 architecture. No functional change in this patch.

---
 mm/vmscan.c | 52 ++++++++++++++++++++++++++++++----------------------
 1 file changed, 30 insertions(+), 22 deletions(-)
mm/vmscan.c +30 -22
index eb23bb1afc64..3b183f704d5d 100644
@@ -4491,12 +4491,37 @@ static void inc_max_seq(struct lruvec *lruvec, bool can_swap, bool force_scan)
 	spin_unlock_irq(&lruvec->lru_lock);
 }
 
+static bool iterate_mm_list_walk(struct lruvec *lruvec, unsigned long max_seq,
+				 bool can_swap, bool force_scan)
+{
+	bool success;
+	struct mm_struct *mm = NULL;
+	struct lru_gen_mm_walk *walk;
+
+	walk = set_mm_walk(NULL, true);
+	if (!walk) {
+		success = iterate_mm_list_nowalk(lruvec, max_seq);
+		return success;
+	}
+
+	walk->lruvec = lruvec;
+	walk->max_seq = max_seq;
+	walk->can_swap = can_swap;
+	walk->force_scan = force_scan;
+
+	do {
+		success = iterate_mm_list(lruvec, walk, &mm);
+		if (mm)
+			walk_mm(lruvec, mm, walk);
+	} while (mm);
+
+	return success;
+}
+
 static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long max_seq,
 			       struct scan_control *sc, bool can_swap, bool force_scan)
 {
 	bool success;
-	struct lru_gen_mm_walk *walk;
-	struct mm_struct *mm = NULL;
 	struct lru_gen_folio *lrugen = &lruvec->lrugen;
 
 	VM_WARN_ON_ONCE(max_seq > READ_ONCE(lrugen->max_seq));
@@ -4506,34 +4531,17 @@ static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long max_seq,
 		success = false;
 		goto done;
 	}
-
 	/*
 	 * If the hardware doesn't automatically set the accessed bit, fallback
 	 * to lru_gen_look_around(), which only clears the accessed bit in a
 	 * handful of PTEs. Spreading the work out over a period of time usually
 	 * is less efficient, but it avoids bursty page faults.
 	 */
-	if (!should_walk_mmu()) {
-		success = iterate_mm_list_nowalk(lruvec, max_seq);
-		goto done;
-	}
-
-	walk = set_mm_walk(NULL, true);
-	if (!walk) {
+	if (!should_walk_mmu())
 		success = iterate_mm_list_nowalk(lruvec, max_seq);
-		goto done;
-	}
-
-	walk->lruvec = lruvec;
-	walk->max_seq = max_seq;
-	walk->can_swap = can_swap;
-	walk->force_scan = force_scan;
+	else
+		success = iterate_mm_list_walk(lruvec, max_seq, can_swap, force_scan);
 
-	do {
-		success = iterate_mm_list(lruvec, walk, &mm);
-		if (mm)
-			walk_mm(lruvec, mm, walk);
-	} while (mm);
 done:
 	if (success)
 		inc_max_seq(lruvec, can_swap, force_scan);
Signature · 1 line
2.41.0
#2 lore raw

PATCH v2 2/5 mm/mglru: Move Bloom filter code around

In reply to Aneesh Kumar K.V

This allows to avoid building this code on powerpc architecture. No functional change in this patch.

---
 mm/vmscan.c | 462 ++++++++++++++++++++++++++--------------------------
 1 file changed, 231 insertions(+), 231 deletions(-)
mm/vmscan.c +231 -231
index 3b183f704d5d..c5fbc3babcd8 100644
@@ -3325,6 +3325,237 @@ static bool __maybe_unused seq_is_valid(struct lruvec *lruvec)
 	       get_nr_gens(lruvec, LRU_GEN_ANON) <= MAX_NR_GENS;
 }
 
+/******************************************************************************
+ *                          PID controller
+ ******************************************************************************/
+
+/*
+ * A feedback loop based on Proportional-Integral-Derivative (PID) controller.
+ *
+ * The P term is refaulted/(evicted+protected) from a tier in the generation
+ * currently being evicted; the I term is the exponential moving average of the
+ * P term over the generations previously evicted, using the smoothing factor
+ * 1/2; the D term isn't supported.
+ *
+ * The setpoint (SP) is always the first tier of one type; the process variable
+ * (PV) is either any tier of the other type or any other tier of the same
+ * type.
+ *
+ * The error is the difference between the SP and the PV; the correction is to
+ * turn off protection when SP>PV or turn on protection when SP<PV.
+ *
+ * For future optimizations:
+ * 1. The D term may discount the other two terms over time so that long-lived
+ *    generations can resist stale information.
+ */
+struct ctrl_pos {
+	unsigned long refaulted;
+	unsigned long total;
+	int gain;
+};
+
+static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,
+			  struct ctrl_pos *pos)
+{
+	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+	int hist = lru_hist_from_seq(lrugen->min_seq[type]);
+
+	pos->refaulted = lrugen->avg_refaulted[type][tier] +
+			 atomic_long_read(&lrugen->refaulted[hist][type][tier]);
+	pos->total = lrugen->avg_total[type][tier] +
+		     atomic_long_read(&lrugen->evicted[hist][type][tier]);
+	if (tier)
+		pos->total += lrugen->protected[hist][type][tier - 1];
+	pos->gain = gain;
+}
+
+static void reset_ctrl_pos(struct lruvec *lruvec, int type, bool carryover)
+{
+	int hist, tier;
+	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+	bool clear = carryover ? NR_HIST_GENS == 1 : NR_HIST_GENS > 1;
+	unsigned long seq = carryover ? lrugen->min_seq[type] : lrugen->max_seq + 1;
+
+	lockdep_assert_held(&lruvec->lru_lock);
+
+	if (!carryover && !clear)
+		return;
+
+	hist = lru_hist_from_seq(seq);
+
+	for (tier = 0; tier < MAX_NR_TIERS; tier++) {
+		if (carryover) {
+			unsigned long sum;
+
+			sum = lrugen->avg_refaulted[type][tier] +
+			      atomic_long_read(&lrugen->refaulted[hist][type][tier]);
+			WRITE_ONCE(lrugen->avg_refaulted[type][tier], sum / 2);
+
+			sum = lrugen->avg_total[type][tier] +
+			      atomic_long_read(&lrugen->evicted[hist][type][tier]);
+			if (tier)
+				sum += lrugen->protected[hist][type][tier - 1];
+			WRITE_ONCE(lrugen->avg_total[type][tier], sum / 2);
+		}
+
+		if (clear) {
+			atomic_long_set(&lrugen->refaulted[hist][type][tier], 0);
+			atomic_long_set(&lrugen->evicted[hist][type][tier], 0);
+			if (tier)
+				WRITE_ONCE(lrugen->protected[hist][type][tier - 1], 0);
+		}
+	}
+}
+
+static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv)
+{
+	/*
+	 * Return true if the PV has a limited number of refaults or a lower
+	 * refaulted/total than the SP.
+	 */
+	return pv->refaulted < MIN_LRU_BATCH ||
+	       pv->refaulted * (sp->total + MIN_LRU_BATCH) * sp->gain <=
+	       (sp->refaulted + 1) * pv->total * pv->gain;
+}
+
+/******************************************************************************
+ *                          the aging
+ ******************************************************************************/
+
+/* protect pages accessed multiple times through file descriptors */
+static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio, bool reclaiming)
+{
+	int type = folio_is_file_lru(folio);
+	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+	int new_gen, old_gen = lru_gen_from_seq(lrugen->min_seq[type]);
+	unsigned long new_flags, old_flags = READ_ONCE(folio->flags);
+
+	VM_WARN_ON_ONCE_FOLIO(!(old_flags & LRU_GEN_MASK), folio);
+
+	do {
+		new_gen = ((old_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+		/* folio_update_gen() has promoted this page? */
+		if (new_gen >= 0 && new_gen != old_gen)
+			return new_gen;
+
+		new_gen = (old_gen + 1) % MAX_NR_GENS;
+
+		new_flags = old_flags & ~(LRU_GEN_MASK | LRU_REFS_MASK | LRU_REFS_FLAGS);
+		new_flags |= (new_gen + 1UL) << LRU_GEN_PGOFF;
+		/* for folio_end_writeback() */
+		if (reclaiming)
+			new_flags |= BIT(PG_reclaim);
+	} while (!try_cmpxchg(&folio->flags, &old_flags, new_flags));
+
+	lru_gen_update_size(lruvec, folio, old_gen, new_gen);
+
+	return new_gen;
+}
+
+static unsigned long get_pte_pfn(pte_t pte, struct vm_area_struct *vma, unsigned long addr)
+{
+	unsigned long pfn = pte_pfn(pte);
+
+	VM_WARN_ON_ONCE(addr < vma->vm_start || addr >= vma->vm_end);
+
+	if (!pte_present(pte) || is_zero_pfn(pfn))
+		return -1;
+
+	if (WARN_ON_ONCE(pte_devmap(pte) || pte_special(pte)))
+		return -1;
+
+	if (WARN_ON_ONCE(!pfn_valid(pfn)))
+		return -1;
+
+	return pfn;
+}
+
+static struct folio *get_pfn_folio(unsigned long pfn, struct mem_cgroup *memcg,
+				   struct pglist_data *pgdat, bool can_swap)
+{
+	struct folio *folio;
+
+	/* try to avoid unnecessary memory loads */
+	if (pfn < pgdat->node_start_pfn || pfn >= pgdat_end_pfn(pgdat))
+		return NULL;
+
+	folio = pfn_folio(pfn);
+	if (folio_nid(folio) != pgdat->node_id)
+		return NULL;
+
+	if (folio_memcg_rcu(folio) != memcg)
+		return NULL;
+
+	/* file VMAs can contain anon pages from COW */
+	if (!folio_is_file_lru(folio) && !can_swap)
+		return NULL;
+
+	return folio;
+}
+
+/* promote pages accessed through page tables */
+static int folio_update_gen(struct folio *folio, int gen)
+{
+	unsigned long new_flags, old_flags = READ_ONCE(folio->flags);
+
+	VM_WARN_ON_ONCE(gen >= MAX_NR_GENS);
+	VM_WARN_ON_ONCE(!rcu_read_lock_held());
+
+	do {
+		/* lru_gen_del_folio() has isolated this page? */
+		if (!(old_flags & LRU_GEN_MASK)) {
+			/* for shrink_folio_list() */
+			new_flags = old_flags | BIT(PG_referenced);
+			continue;
+		}
+
+		new_flags = old_flags & ~(LRU_GEN_MASK | LRU_REFS_MASK | LRU_REFS_FLAGS);
+		new_flags |= (gen + 1UL) << LRU_GEN_PGOFF;
+	} while (!try_cmpxchg(&folio->flags, &old_flags, new_flags));
+
+	return ((old_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
+}
+
+static void update_batch_size(struct lru_gen_mm_walk *walk, struct folio *folio,
+			      int old_gen, int new_gen)
+{
+	int type = folio_is_file_lru(folio);
+	int zone = folio_zonenum(folio);
+	int delta = folio_nr_pages(folio);
+
+	VM_WARN_ON_ONCE(old_gen >= MAX_NR_GENS);
+	VM_WARN_ON_ONCE(new_gen >= MAX_NR_GENS);
+
+	walk->batched++;
+
+	walk->nr_pages[old_gen][type][zone] -= delta;
+	walk->nr_pages[new_gen][type][zone] += delta;
+}
+
+static void reset_batch_size(struct lruvec *lruvec, struct lru_gen_mm_walk *walk)
+{
+	int gen, type, zone;
+	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+
+	walk->batched = 0;
+
+	for_each_gen_type_zone(gen, type, zone) {
+		enum lru_list lru = type * LRU_INACTIVE_FILE;
+		int delta = walk->nr_pages[gen][type][zone];
+
+		if (!delta)
+			continue;
+
+		walk->nr_pages[gen][type][zone] = 0;
+		WRITE_ONCE(lrugen->nr_pages[gen][type][zone],
+			   lrugen->nr_pages[gen][type][zone] + delta);
+
+		if (lru_gen_is_active(lruvec, gen))
+			lru += LRU_ACTIVE;
+		__update_lru_size(lruvec, lru, zone, delta);
+	}
+}
+
 /******************************************************************************
  *                          Bloom filters
  ******************************************************************************/
@@ -3672,237 +3903,6 @@ static bool iterate_mm_list_nowalk(struct lruvec *lruvec, unsigned long max_seq)
 	return success;
 }
 
-/******************************************************************************
- *                          PID controller
- ******************************************************************************/
-
-/*
- * A feedback loop based on Proportional-Integral-Derivative (PID) controller.
- *
- * The P term is refaulted/(evicted+protected) from a tier in the generation
- * currently being evicted; the I term is the exponential moving average of the
- * P term over the generations previously evicted, using the smoothing factor
- * 1/2; the D term isn't supported.
- *
- * The setpoint (SP) is always the first tier of one type; the process variable
- * (PV) is either any tier of the other type or any other tier of the same
- * type.
- *
- * The error is the difference between the SP and the PV; the correction is to
- * turn off protection when SP>PV or turn on protection when SP<PV.
- *
- * For future optimizations:
- * 1. The D term may discount the other two terms over time so that long-lived
- *    generations can resist stale information.
- */
-struct ctrl_pos {
-	unsigned long refaulted;
-	unsigned long total;
-	int gain;
-};
-
-static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,
-			  struct ctrl_pos *pos)
-{
-	struct lru_gen_folio *lrugen = &lruvec->lrugen;
-	int hist = lru_hist_from_seq(lrugen->min_seq[type]);
-
-	pos->refaulted = lrugen->avg_refaulted[type][tier] +
-			 atomic_long_read(&lrugen->refaulted[hist][type][tier]);
-	pos->total = lrugen->avg_total[type][tier] +
-		     atomic_long_read(&lrugen->evicted[hist][type][tier]);
-	if (tier)
-		pos->total += lrugen->protected[hist][type][tier - 1];
-	pos->gain = gain;
-}
-
-static void reset_ctrl_pos(struct lruvec *lruvec, int type, bool carryover)
-{
-	int hist, tier;
-	struct lru_gen_folio *lrugen = &lruvec->lrugen;
-	bool clear = carryover ? NR_HIST_GENS == 1 : NR_HIST_GENS > 1;
-	unsigned long seq = carryover ? lrugen->min_seq[type] : lrugen->max_seq + 1;
-
-	lockdep_assert_held(&lruvec->lru_lock);
-
-	if (!carryover && !clear)
-		return;
-
-	hist = lru_hist_from_seq(seq);
-
-	for (tier = 0; tier < MAX_NR_TIERS; tier++) {
-		if (carryover) {
-			unsigned long sum;
-
-			sum = lrugen->avg_refaulted[type][tier] +
-			      atomic_long_read(&lrugen->refaulted[hist][type][tier]);
-			WRITE_ONCE(lrugen->avg_refaulted[type][tier], sum / 2);
-
-			sum = lrugen->avg_total[type][tier] +
-			      atomic_long_read(&lrugen->evicted[hist][type][tier]);
-			if (tier)
-				sum += lrugen->protected[hist][type][tier - 1];
-			WRITE_ONCE(lrugen->avg_total[type][tier], sum / 2);
-		}
-
-		if (clear) {
-			atomic_long_set(&lrugen->refaulted[hist][type][tier], 0);
-			atomic_long_set(&lrugen->evicted[hist][type][tier], 0);
-			if (tier)
-				WRITE_ONCE(lrugen->protected[hist][type][tier - 1], 0);
-		}
-	}
-}
-
-static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv)
-{
-	/*
-	 * Return true if the PV has a limited number of refaults or a lower
-	 * refaulted/total than the SP.
-	 */
-	return pv->refaulted < MIN_LRU_BATCH ||
-	       pv->refaulted * (sp->total + MIN_LRU_BATCH) * sp->gain <=
-	       (sp->refaulted + 1) * pv->total * pv->gain;
-}
-
-/******************************************************************************
- *                          the aging
- ******************************************************************************/
-
-/* protect pages accessed multiple times through file descriptors */
-static int folio_inc_gen(struct lruvec *lruvec, struct folio *folio, bool reclaiming)
-{
-	int type = folio_is_file_lru(folio);
-	struct lru_gen_folio *lrugen = &lruvec->lrugen;
-	int new_gen, old_gen = lru_gen_from_seq(lrugen->min_seq[type]);
-	unsigned long new_flags, old_flags = READ_ONCE(folio->flags);
-
-	VM_WARN_ON_ONCE_FOLIO(!(old_flags & LRU_GEN_MASK), folio);
-
-	do {
-		new_gen = ((old_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
-		/* folio_update_gen() has promoted this page? */
-		if (new_gen >= 0 && new_gen != old_gen)
-			return new_gen;
-
-		new_gen = (old_gen + 1) % MAX_NR_GENS;
-
-		new_flags = old_flags & ~(LRU_GEN_MASK | LRU_REFS_MASK | LRU_REFS_FLAGS);
-		new_flags |= (new_gen + 1UL) << LRU_GEN_PGOFF;
-		/* for folio_end_writeback() */
-		if (reclaiming)
-			new_flags |= BIT(PG_reclaim);
-	} while (!try_cmpxchg(&folio->flags, &old_flags, new_flags));
-
-	lru_gen_update_size(lruvec, folio, old_gen, new_gen);
-
-	return new_gen;
-}
-
-static unsigned long get_pte_pfn(pte_t pte, struct vm_area_struct *vma, unsigned long addr)
-{
-	unsigned long pfn = pte_pfn(pte);
-
-	VM_WARN_ON_ONCE(addr < vma->vm_start || addr >= vma->vm_end);
-
-	if (!pte_present(pte) || is_zero_pfn(pfn))
-		return -1;
-
-	if (WARN_ON_ONCE(pte_devmap(pte) || pte_special(pte)))
-		return -1;
-
-	if (WARN_ON_ONCE(!pfn_valid(pfn)))
-		return -1;
-
-	return pfn;
-}
-
-static struct folio *get_pfn_folio(unsigned long pfn, struct mem_cgroup *memcg,
-				   struct pglist_data *pgdat, bool can_swap)
-{
-	struct folio *folio;
-
-	/* try to avoid unnecessary memory loads */
-	if (pfn < pgdat->node_start_pfn || pfn >= pgdat_end_pfn(pgdat))
-		return NULL;
-
-	folio = pfn_folio(pfn);
-	if (folio_nid(folio) != pgdat->node_id)
-		return NULL;
-
-	if (folio_memcg_rcu(folio) != memcg)
-		return NULL;
-
-	/* file VMAs can contain anon pages from COW */
-	if (!folio_is_file_lru(folio) && !can_swap)
-		return NULL;
-
-	return folio;
-}
-
-/* promote pages accessed through page tables */
-static int folio_update_gen(struct folio *folio, int gen)
-{
-	unsigned long new_flags, old_flags = READ_ONCE(folio->flags);
-
-	VM_WARN_ON_ONCE(gen >= MAX_NR_GENS);
-	VM_WARN_ON_ONCE(!rcu_read_lock_held());
-
-	do {
-		/* lru_gen_del_folio() has isolated this page? */
-		if (!(old_flags & LRU_GEN_MASK)) {
-			/* for shrink_folio_list() */
-			new_flags = old_flags | BIT(PG_referenced);
-			continue;
-		}
-
-		new_flags = old_flags & ~(LRU_GEN_MASK | LRU_REFS_MASK | LRU_REFS_FLAGS);
-		new_flags |= (gen + 1UL) << LRU_GEN_PGOFF;
-	} while (!try_cmpxchg(&folio->flags, &old_flags, new_flags));
-
-	return ((old_flags & LRU_GEN_MASK) >> LRU_GEN_PGOFF) - 1;
-}
-
-static void update_batch_size(struct lru_gen_mm_walk *walk, struct folio *folio,
-			      int old_gen, int new_gen)
-{
-	int type = folio_is_file_lru(folio);
-	int zone = folio_zonenum(folio);
-	int delta = folio_nr_pages(folio);
-
-	VM_WARN_ON_ONCE(old_gen >= MAX_NR_GENS);
-	VM_WARN_ON_ONCE(new_gen >= MAX_NR_GENS);
-
-	walk->batched++;
-
-	walk->nr_pages[old_gen][type][zone] -= delta;
-	walk->nr_pages[new_gen][type][zone] += delta;
-}
-
-static void reset_batch_size(struct lruvec *lruvec, struct lru_gen_mm_walk *walk)
-{
-	int gen, type, zone;
-	struct lru_gen_folio *lrugen = &lruvec->lrugen;
-
-	walk->batched = 0;
-
-	for_each_gen_type_zone(gen, type, zone) {
-		enum lru_list lru = type * LRU_INACTIVE_FILE;
-		int delta = walk->nr_pages[gen][type][zone];
-
-		if (!delta)
-			continue;
-
-		walk->nr_pages[gen][type][zone] = 0;
-		WRITE_ONCE(lrugen->nr_pages[gen][type][zone],
-			   lrugen->nr_pages[gen][type][zone] + delta);
-
-		if (lru_gen_is_active(lruvec, gen))
-			lru += LRU_ACTIVE;
-		__update_lru_size(lruvec, lru, zone, delta);
-	}
-}
-
 static int should_skip_vma(unsigned long start, unsigned long end, struct mm_walk *args)
 {
 	struct address_space *mapping;
Signature · 1 line
2.41.0
#3 lore raw

PATCH v2 3/5 mm/mglru: Move code around to make future patch easy

In reply to Aneesh Kumar K.V

No functional change in this patch.

---
 mm/vmscan.c | 64 ++++++++++++++++++++++++++---------------------------
 1 file changed, 32 insertions(+), 32 deletions(-)
mm/vmscan.c +32 -32
index c5fbc3babcd8..a846a62df0ba 100644
@@ -3556,6 +3556,38 @@ static void reset_batch_size(struct lruvec *lruvec, struct lru_gen_mm_walk *walk
 	}
 }
 
+static struct lru_gen_mm_walk *set_mm_walk(struct pglist_data *pgdat, bool force_alloc)
+{
+	struct lru_gen_mm_walk *walk = current->reclaim_state->mm_walk;
+
+	if (pgdat && current_is_kswapd()) {
+		VM_WARN_ON_ONCE(walk);
+
+		walk = &pgdat->mm_walk;
+	} else if (!walk && force_alloc) {
+		VM_WARN_ON_ONCE(current_is_kswapd());
+
+		walk = kzalloc(sizeof(*walk), __GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN);
+	}
+
+	current->reclaim_state->mm_walk = walk;
+
+	return walk;
+}
+
+static void clear_mm_walk(void)
+{
+	struct lru_gen_mm_walk *walk = current->reclaim_state->mm_walk;
+
+	VM_WARN_ON_ONCE(walk && memchr_inv(walk->nr_pages, 0, sizeof(walk->nr_pages)));
+	VM_WARN_ON_ONCE(walk && memchr_inv(walk->mm_stats, 0, sizeof(walk->mm_stats)));
+
+	current->reclaim_state->mm_walk = NULL;
+
+	if (!current_is_kswapd())
+		kfree(walk);
+}
+
 /******************************************************************************
  *                          Bloom filters
  ******************************************************************************/
@@ -4324,38 +4356,6 @@ static void walk_mm(struct lruvec *lruvec, struct mm_struct *mm, struct lru_gen_
 	} while (err == -EAGAIN);
 }
 
-static struct lru_gen_mm_walk *set_mm_walk(struct pglist_data *pgdat, bool force_alloc)
-{
-	struct lru_gen_mm_walk *walk = current->reclaim_state->mm_walk;
-
-	if (pgdat && current_is_kswapd()) {
-		VM_WARN_ON_ONCE(walk);
-
-		walk = &pgdat->mm_walk;
-	} else if (!walk && force_alloc) {
-		VM_WARN_ON_ONCE(current_is_kswapd());
-
-		walk = kzalloc(sizeof(*walk), __GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN);
-	}
-
-	current->reclaim_state->mm_walk = walk;
-
-	return walk;
-}
-
-static void clear_mm_walk(void)
-{
-	struct lru_gen_mm_walk *walk = current->reclaim_state->mm_walk;
-
-	VM_WARN_ON_ONCE(walk && memchr_inv(walk->nr_pages, 0, sizeof(walk->nr_pages)));
-	VM_WARN_ON_ONCE(walk && memchr_inv(walk->mm_stats, 0, sizeof(walk->mm_stats)));
-
-	current->reclaim_state->mm_walk = NULL;
-
-	if (!current_is_kswapd())
-		kfree(walk);
-}
-
 static bool inc_min_seq(struct lruvec *lruvec, int type, bool can_swap)
 {
 	int zone;
Signature · 1 line
2.41.0
#4 lore raw

PATCH v2 4/5 mm/mglru: move iterate_mm_list_walk Helper

In reply to Aneesh Kumar K.V

This helps to avoid building this code on powerpc. No functional change in this patch.

---
 mm/vmscan.c | 54 ++++++++++++++++++++++++++---------------------------
 1 file changed, 27 insertions(+), 27 deletions(-)
mm/vmscan.c +27 -27
index a846a62df0ba..0ea7a07990d3 100644
@@ -4356,6 +4356,33 @@ static void walk_mm(struct lruvec *lruvec, struct mm_struct *mm, struct lru_gen_
 	} while (err == -EAGAIN);
 }
 
+static bool iterate_mm_list_walk(struct lruvec *lruvec, unsigned long max_seq,
+				 bool can_swap, bool force_scan)
+{
+	bool success;
+	struct mm_struct *mm = NULL;
+	struct lru_gen_mm_walk *walk;
+
+	walk = set_mm_walk(NULL, true);
+	if (!walk) {
+		success = iterate_mm_list_nowalk(lruvec, max_seq);
+		return success;
+	}
+
+	walk->lruvec = lruvec;
+	walk->max_seq = max_seq;
+	walk->can_swap = can_swap;
+	walk->force_scan = force_scan;
+
+	do {
+		success = iterate_mm_list(lruvec, walk, &mm);
+		if (mm)
+			walk_mm(lruvec, mm, walk);
+	} while (mm);
+
+	return success;
+}
+
 static bool inc_min_seq(struct lruvec *lruvec, int type, bool can_swap)
 {
 	int zone;
@@ -4491,33 +4518,6 @@ static void inc_max_seq(struct lruvec *lruvec, bool can_swap, bool force_scan)
 	spin_unlock_irq(&lruvec->lru_lock);
 }
 
-static bool iterate_mm_list_walk(struct lruvec *lruvec, unsigned long max_seq,
-				 bool can_swap, bool force_scan)
-{
-	bool success;
-	struct mm_struct *mm = NULL;
-	struct lru_gen_mm_walk *walk;
-
-	walk = set_mm_walk(NULL, true);
-	if (!walk) {
-		success = iterate_mm_list_nowalk(lruvec, max_seq);
-		return success;
-	}
-
-	walk->lruvec = lruvec;
-	walk->max_seq = max_seq;
-	walk->can_swap = can_swap;
-	walk->force_scan = force_scan;
-
-	do {
-		success = iterate_mm_list(lruvec, walk, &mm);
-		if (mm)
-			walk_mm(lruvec, mm, walk);
-	} while (mm);
-
-	return success;
-}
-
 static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long max_seq,
 			       struct scan_control *sc, bool can_swap, bool force_scan)
 {
Signature · 1 line
2.41.0
#5 lore raw

PATCH v2 5/5 mm/mglru: Don't build multi-gen LRU page table walk code on architecture not supported

In reply to Aneesh Kumar K.V

Not all architecture supports hardware atomic updates of access bits. On such an arch, we don't use a page table walk to classify pages into generations. Add a kernel config option and remove adding all the page table walk code on such architecture.

This avoid calling lru_gen related code (lru_gen_add/remove/migrate_mm) in fork/exit/context switch Also we don't build different components like Bloom filter and all the page table walk code (walk_mm and related code) on not supported architecture with this change.

No preformance change observed with mongodb ycsb test:

Patch details Throughput(Ops/sec)
without patch 91252
With patch 91488

Without patch:

$ size mm/vmscan.o
   text    data     bss     dec     hex filename
 116016   36857      40  152913   25551 mm/vmscan.o

With patch

$ size  mm/vmscan.o
   text    data     bss     dec     hex filename
 112864   36437      40  149341   2475d mm/vmscan.o
---
 arch/Kconfig               |  3 +++
 arch/arm64/Kconfig         |  1 +
 arch/x86/Kconfig           |  1 +
 include/linux/memcontrol.h |  2 +-
 include/linux/mm_types.h   | 10 ++++-----
 include/linux/mmzone.h     | 12 +++++++++-
 kernel/fork.c              |  2 +-
 mm/memcontrol.c            |  2 +-
 mm/vmscan.c                | 45 ++++++++++++++++++++++++++++++++++++++
 9 files changed, 69 insertions(+), 9 deletions(-)
arch/Kconfig +3 -0
index aff2746c8af2..ec8662e2f3cb 100644
@@ -1471,6 +1471,9 @@ config DYNAMIC_SIGFRAME
 config HAVE_ARCH_NODE_DEV_GROUP
 	bool
 
+config LRU_TASK_PAGE_AGING
+	bool
+
 config ARCH_HAS_NONLEAF_PMD_YOUNG
 	bool
 	help
arch/arm64/Kconfig +1 -0
index 7856c3a3e35a..d6b5d1647baa 100644
@@ -233,6 +233,7 @@ config ARM64
 	select IRQ_FORCED_THREADING
 	select KASAN_VMALLOC if KASAN
 	select LOCK_MM_AND_FIND_VMA
+	select LRU_TASK_PAGE_AGING		if LRU_GEN
 	select MODULES_USE_ELF_RELA
 	select NEED_DMA_MAP_STATE
 	select NEED_SG_DMA_LENGTH
arch/x86/Kconfig +1 -0
index 7422db409770..940d86a0a566 100644
@@ -281,6 +281,7 @@ config X86
 	select HOTPLUG_SPLIT_STARTUP		if SMP && X86_32
 	select IRQ_FORCED_THREADING
 	select LOCK_MM_AND_FIND_VMA
+	select LRU_TASK_PAGE_AGING		if LRU_GEN
 	select NEED_PER_CPU_EMBED_FIRST_CHUNK
 	select NEED_PER_CPU_PAGE_FIRST_CHUNK
 	select NEED_SG_DMA_LENGTH
include/linux/memcontrol.h +1 -1
index 0ab426a5696b..5ddc1abe95ae 100644
@@ -321,7 +321,7 @@ struct mem_cgroup {
 	struct deferred_split deferred_split_queue;
 #endif
 
-#ifdef CONFIG_LRU_GEN
+#ifdef CONFIG_LRU_TASK_PAGE_AGING
 	/* per-memcg mm_struct list */
 	struct lru_gen_mm_list mm_list;
 #endif
include/linux/mm_types.h +5 -5
index de10fc797c8e..9089762aa8e2 100644
@@ -793,7 +793,7 @@ struct mm_struct {
 		 */
 		unsigned long ksm_rmap_items;
 #endif
-#ifdef CONFIG_LRU_GEN
+#ifdef CONFIG_LRU_TASK_PAGE_AGING
 		struct {
 			/* this mm_struct is on lru_gen_mm_list */
 			struct list_head list;
@@ -808,7 +808,7 @@ struct mm_struct {
 			struct mem_cgroup *memcg;
 #endif
 		} lru_gen;
-#endif /* CONFIG_LRU_GEN */
+#endif /* CONFIG_LRU_TASK_PAGE_AGING */
 	} __randomize_layout;
 
 	/*
@@ -837,7 +837,7 @@ static inline cpumask_t *mm_cpumask(struct mm_struct *mm)
 	return (struct cpumask *)&mm->cpu_bitmap;
 }
 
-#ifdef CONFIG_LRU_GEN
+#ifdef CONFIG_LRU_TASK_PAGE_AGING
 
 struct lru_gen_mm_list {
 	/* mm_struct list for page table walkers */
@@ -871,7 +871,7 @@ static inline void lru_gen_use_mm(struct mm_struct *mm)
 	WRITE_ONCE(mm->lru_gen.bitmap, -1);
 }
 
-#else /* !CONFIG_LRU_GEN */
+#else /* !CONFIG_LRU_TASK_PAGE_AGING */
 
 static inline void lru_gen_add_mm(struct mm_struct *mm)
 {
@@ -895,7 +895,7 @@ static inline void lru_gen_use_mm(struct mm_struct *mm)
 {
 }
 
-#endif /* CONFIG_LRU_GEN */
+#endif /* CONFIG_LRU_TASK_PAGE_AGING */
 
 struct vma_iterator {
 	struct ma_state mas;
include/linux/mmzone.h +11 -1
index 5e50b78d58ea..5300696d7c2c 100644
@@ -461,6 +461,7 @@ enum {
 struct lru_gen_mm_state {
 	/* set to max_seq after each iteration */
 	unsigned long seq;
+#ifdef CONFIG_LRU_TASK_PAGE_AGING
 	/* where the current iteration continues after */
 	struct list_head *head;
 	/* where the last iteration ended before */
@@ -469,6 +470,11 @@ struct lru_gen_mm_state {
 	unsigned long *filters[NR_BLOOM_FILTERS];
 	/* the mm stats for debugging */
 	unsigned long stats[NR_HIST_GENS][NR_MM_STATS];
+#else
+	/* protect the seq update above */
+	/* May be we can use lruvec->lock?  */
+	spinlock_t lock;
+#endif
 };
 
 struct lru_gen_mm_walk {
@@ -546,9 +552,13 @@ struct lru_gen_memcg {
 };
 
 void lru_gen_init_pgdat(struct pglist_data *pgdat);
-
+#ifdef CONFIG_LRU_TASK_PAGE_AGING
 void lru_gen_init_memcg(struct mem_cgroup *memcg);
 void lru_gen_exit_memcg(struct mem_cgroup *memcg);
+#else
+static inline void lru_gen_init_memcg(struct mem_cgroup *memcg) {}
+static inline void lru_gen_exit_memcg(struct mem_cgroup *memcg) {}
+#endif
 void lru_gen_online_memcg(struct mem_cgroup *memcg);
 void lru_gen_offline_memcg(struct mem_cgroup *memcg);
 void lru_gen_release_memcg(struct mem_cgroup *memcg);
kernel/fork.c +1 -1
index b85814e614a5..c7e8f65a72c8 100644
@@ -2932,7 +2932,7 @@ pid_t kernel_clone(struct kernel_clone_args *args)
 		get_task_struct(p);
 	}
 
-	if (IS_ENABLED(CONFIG_LRU_GEN) && !(clone_flags & CLONE_VM)) {
+	if (IS_ENABLED(CONFIG_LRU_TASK_PAGE_AGING) && !(clone_flags & CLONE_VM)) {
 		/* lock the task to synchronize with memcg migration */
 		task_lock(p);
 		lru_gen_add_mm(p->mm);
mm/memcontrol.c +1 -1
index 92898e99e8a5..cdcf1b6baf3e 100644
@@ -6357,7 +6357,7 @@ static void mem_cgroup_move_task(void)
 }
 #endif
 
-#ifdef CONFIG_LRU_GEN
+#ifdef CONFIG_LRU_TASK_PAGE_AGING
 static void mem_cgroup_attach(struct cgroup_taskset *tset)
 {
 	struct task_struct *task;
mm/vmscan.c +45 -0

106 lines not on this page · raw message

Signature · 1 line
2.41.0
#6 lore raw
In reply to Aneesh Kumar K.V
Quoted 13 lines · Aneesh Kumar K.V

This patchset avoids building changes added by commit bd74fdaea146 ("mm:
multi-gen LRU: support page table walks") on platforms that don't support hardware atomic updates of access bits.

Aneesh Kumar K.V (5):
mm/mglru: Create a new helper iterate_mm_list_walk mm/mglru: Move Bloom filter code around
mm/mglru: Move code around to make future patch easy mm/mglru: move iterate_mm_list_walk Helper mm/mglru: Don't build multi-gen LRU page table walk code on
architecture not supported

Quoted 10 lines
 arch/Kconfig               |   3 +
 arch/arm64/Kconfig         |   1 +
 arch/x86/Kconfig           |   1 +
 include/linux/memcontrol.h |   2 +-
 include/linux/mm_types.h   |  10 +-
 include/linux/mmzone.h     |  12 +-
 kernel/fork.c              |   2 +-
 mm/memcontrol.c            |   2 +-
 mm/vmscan.c                | 955 +++++++++++++++++++------------------
 9 files changed, 528 insertions(+), 460 deletions(-)

1. There is no need for a new Kconfig -- the condition is simply defined(CONFIG_LRU_GEN) && !defined(arch_has_hw_pte_young)

2. The best practice to disable static functions is not by macros but:

    static int const_cond(void)
    {
        return 1;
    }

    int main(void)
    {
        int a = const_cond();

        if (a)
                return 0;

        /* the compiler doesn't generate code for static funcs below */
        static_func_1();
        ...
        static_func_N();

LTO also optimizes external functions. But not everyone uses it. So we still need macros for them, and of course data structures.

3. In 4/5, you have:

    @@ -461,6 +461,7 @@ enum {
     struct lru_gen_mm_state {
        /* set to max_seq after each iteration */
        unsigned long seq;
    +#ifdef CONFIG_LRU_TASK_PAGE_AGING
        /* where the current iteration continues after */
        struct list_head *head;
        /* where the last iteration ended before */
    @@ -469,6 +470,11 @@ struct lru_gen_mm_state {
        unsigned long *filters[NR_BLOOM_FILTERS];
        /* the mm stats for debugging */
        unsigned long stats[NR_HIST_GENS][NR_MM_STATS];
    +#else
    +   /* protect the seq update above */
    +   /* May be we can use lruvec->lock?  */
    +   spinlock_t lock;
    +#endif
     };

The answer is yes, and not only that, we don't need lru_gen_mm_state at all.

I'm attaching a patch that fixes all above. If you want to post it, please feel free -- fully test it please, since I didn't. Otherwise I can ask TJ to help make this work for you.

$ git diff --stat
 include/linux/memcontrol.h |   2 +-
 include/linux/mm_types.h   |  12 +-
 include/linux/mmzone.h     |   2 +
 kernel/bounds.c            |   6 +-
 kernel/fork.c              |   2 +-
 mm/vmscan.c                | 169 +++++++++++++++++++--------
 6 files changed, 137 insertions(+), 56 deletions(-)

On x86:

$ ./scripts/bloat-o-meter mm/vmscan.o.old mm/vmscan.o
add/remove: 24/34 grow/shrink: 2/7 up/down: 966/-8716 (-7750)
Function                                     old     new   delta
...
should_skip_vma                              206       -    -206
get_pte_pfn                                  261       -    -261
lru_gen_add_mm                               323       -    -323
lru_gen_seq_show                            1710    1370    -340
lru_gen_del_mm                               432       -    -432
reset_batch_size                             572       -    -572
try_to_inc_max_seq                          2947    1635   -1312
walk_pmd_range_locked                       1508       -   -1508
walk_pud_range                              3238       -   -3238
Total: Before=99449, After=91699, chg -7.79%
$ objdump -S mm/vmscan.o | grep -A 20 "<try_to_inc_max_seq>:"
000000000000a350 <try_to_inc_max_seq>:
{
    a350: e8 00 00 00 00        call   a355 <try_to_inc_max_seq+0x5>
    a355: 55                    push   %rbp
    a356: 48 89 e5              mov    %rsp,%rbp
    a359: 41 57                push   %r15
    a35b: 41 56                push   %r14
    a35d: 41 55                push   %r13
    a35f: 41 54                push   %r12
    a361: 53                    push   %rbx
    a362: 48 83 ec 70          sub    $0x70,%rsp
    a366: 41 89 d4              mov    %edx,%r12d
    a369: 49 89 f6              mov    %rsi,%r14
    a36c: 49 89 ff              mov    %rdi,%r15
spin_lock_irq(&lruvec->lru_lock);
    a36f: 48 8d 5f 50          lea    0x50(%rdi),%rbx
    a373: 48 89 df              mov    %rbx,%rdi
    a376: e8 00 00 00 00        call   a37b <try_to_inc_max_seq+0x2b>
success = max_seq == lrugen->max_seq;
    a37b: 49 8b 87 88 00 00 00 mov    0x88(%r15),%rax
    a382: 4c 39 f0              cmp    %r14,%rax
include/linux/memcontrol.h +1 -1
index 634a282099bf..c55156b76a92 100644
@@ -321,7 +321,7 @@ struct mem_cgroup {
 	struct deferred_split deferred_split_queue;
 #endif
 
-#ifdef CONFIG_LRU_GEN
+#ifdef LRU_GEN_WALKS_PGTABLES
 	/* per-memcg mm_struct list */
 	struct lru_gen_mm_list mm_list;
 #endif
include/linux/mm_types.h +8 -4
index fb68f7b8efb6..fbf5050a36f0 100644
@@ -798,7 +798,7 @@ struct mm_struct {
 		 */
 		unsigned long ksm_zero_pages;
 #endif /* CONFIG_KSM */
-#ifdef CONFIG_LRU_GEN
+#ifdef LRU_GEN_WALKS_PGTABLES
 		struct {
 			/* this mm_struct is on lru_gen_mm_list */
 			struct list_head list;
@@ -813,7 +813,7 @@ struct mm_struct {
 			struct mem_cgroup *memcg;
 #endif
 		} lru_gen;
-#endif /* CONFIG_LRU_GEN */
+#endif /* LRU_GEN_WALKS_PGTABLES */
 	} __randomize_layout;
 
 	/*
@@ -851,6 +851,10 @@ struct lru_gen_mm_list {
 	spinlock_t lock;
 };
 
+#endif /* CONFIG_LRU_GEN */
+
+#ifdef LRU_GEN_WALKS_PGTABLES
+
 void lru_gen_add_mm(struct mm_struct *mm);
 void lru_gen_del_mm(struct mm_struct *mm);
 #ifdef CONFIG_MEMCG
@@ -876,7 +880,7 @@ static inline void lru_gen_use_mm(struct mm_struct *mm)
 	WRITE_ONCE(mm->lru_gen.bitmap, -1);
 }
 
-#else /* !CONFIG_LRU_GEN */
+#else /* !LRU_GEN_WALKS_PGTABLES */
 
 static inline void lru_gen_add_mm(struct mm_struct *mm)
 {
@@ -900,7 +904,7 @@ static inline void lru_gen_use_mm(struct mm_struct *mm)
 {
 }
 
-#endif /* CONFIG_LRU_GEN */
+#endif /* LRU_GEN_WALKS_PGTABLES */
 
 struct vma_iterator {
 	struct ma_state mas;
include/linux/mmzone.h +2 -0
index 4106fbc5b4b3..720f913747fe 100644
@@ -631,9 +631,11 @@ struct lruvec {
 #ifdef CONFIG_LRU_GEN
 	/* evictable pages divided into generations */
 	struct lru_gen_folio		lrugen;
+#ifdef LRU_GEN_WALKS_PGTABLES
 	/* to concurrently iterate lru_gen_mm_list */
 	struct lru_gen_mm_state		mm_state;
 #endif
+#endif /* CONFIG_LRU_GEN */
 #ifdef CONFIG_MEMCG
 	struct pglist_data *pgdat;
 #endif
kernel/bounds.c +5 -1
index b529182e8b04..3ee2a4273fea 100644
@@ -12,6 +12,7 @@
 #include <linux/kbuild.h>
 #include <linux/log2.h>
 #include <linux/spinlock_types.h>
+#include <linux/pgtable.h>
 
 int main(void)
 {
@@ -25,7 +26,10 @@ int main(void)
 #ifdef CONFIG_LRU_GEN
 	DEFINE(LRU_GEN_WIDTH, order_base_2(MAX_NR_GENS + 1));
 	DEFINE(__LRU_REFS_WIDTH, MAX_NR_TIERS - 2);
-#else
+#ifdef arch_has_hw_pte_young
+	DEFINE(LRU_GEN_WALKS_PGTABLES, 1);
+#endif
+#else /* !CONFIG_LRU_GEN */
 	DEFINE(LRU_GEN_WIDTH, 0);
 	DEFINE(__LRU_REFS_WIDTH, 0);
 #endif
kernel/fork.c +1 -1
index 2ba918f83bde..930cb4ee9172 100644
@@ -2938,7 +2938,7 @@ pid_t kernel_clone(struct kernel_clone_args *args)
 		get_task_struct(p);
 	}
 
-	if (IS_ENABLED(CONFIG_LRU_GEN) && !(clone_flags & CLONE_VM)) {
+	if (IS_ENABLED(LRU_GEN_WALKS_PGTABLES) && !(clone_flags & CLONE_VM)) {
 		/* lock the task to synchronize with memcg migration */
 		task_lock(p);
 		lru_gen_add_mm(p->mm);
mm/vmscan.c +120 -49

411 lines not on this page · raw message

#7 lore raw
Quoted 118 lines · Yu Zhao
Quoted 13 lines · Aneesh Kumar K.V

This patchset avoids building changes added by commit bd74fdaea146 ("mm:
multi-gen LRU: support page table walks") on platforms that don't support hardware atomic updates of access bits.

Aneesh Kumar K.V (5):
mm/mglru: Create a new helper iterate_mm_list_walk mm/mglru: Move Bloom filter code around
mm/mglru: Move code around to make future patch easy mm/mglru: move iterate_mm_list_walk Helper mm/mglru: Don't build multi-gen LRU page table walk code on
architecture not supported

Quoted 10 lines
 arch/Kconfig               |   3 +
 arch/arm64/Kconfig         |   1 +
 arch/x86/Kconfig           |   1 +
 include/linux/memcontrol.h |   2 +-
 include/linux/mm_types.h   |  10 +-
 include/linux/mmzone.h     |  12 +-
 kernel/fork.c              |   2 +-
 mm/memcontrol.c            |   2 +-
 mm/vmscan.c                | 955 +++++++++++++++++++------------------
 9 files changed, 528 insertions(+), 460 deletions(-)

1. There is no need for a new Kconfig -- the condition is simply defined(CONFIG_LRU_GEN) && !defined(arch_has_hw_pte_young)

2. The best practice to disable static functions is not by macros but:

    static int const_cond(void)
    {
        return 1;
    }

    int main(void)
    {
        int a = const_cond();

        if (a)
                return 0;

        /* the compiler doesn't generate code for static funcs below */
        static_func_1();
        ...
        static_func_N();

LTO also optimizes external functions. But not everyone uses it. So we still need macros for them, and of course data structures.

3. In 4/5, you have:

    @@ -461,6 +461,7 @@ enum {
     struct lru_gen_mm_state {
        /* set to max_seq after each iteration */
        unsigned long seq;
    +#ifdef CONFIG_LRU_TASK_PAGE_AGING
        /* where the current iteration continues after */
        struct list_head *head;
        /* where the last iteration ended before */
    @@ -469,6 +470,11 @@ struct lru_gen_mm_state {
        unsigned long *filters[NR_BLOOM_FILTERS];
        /* the mm stats for debugging */
        unsigned long stats[NR_HIST_GENS][NR_MM_STATS];
    +#else
    +   /* protect the seq update above */
    +   /* May be we can use lruvec->lock?  */
    +   spinlock_t lock;
    +#endif
     };

The answer is yes, and not only that, we don't need lru_gen_mm_state at all.

I'm attaching a patch that fixes all above. If you want to post it, please feel free -- fully test it please, since I didn't. Otherwise I can ask TJ to help make this work for you.

$ git diff --stat
 include/linux/memcontrol.h |   2 +-
 include/linux/mm_types.h   |  12 +-
 include/linux/mmzone.h     |   2 +
 kernel/bounds.c            |   6 +-
 kernel/fork.c              |   2 +-
 mm/vmscan.c                | 169 +++++++++++++++++++--------
 6 files changed, 137 insertions(+), 56 deletions(-)

On x86:

$ ./scripts/bloat-o-meter mm/vmscan.o.old mm/vmscan.o
add/remove: 24/34 grow/shrink: 2/7 up/down: 966/-8716 (-7750)
Function                                     old     new   delta
...
should_skip_vma                              206       -    -206
get_pte_pfn                                  261       -    -261
lru_gen_add_mm                               323       -    -323
lru_gen_seq_show                            1710    1370    -340
lru_gen_del_mm                               432       -    -432
reset_batch_size                             572       -    -572
try_to_inc_max_seq                          2947    1635   -1312
walk_pmd_range_locked                       1508       -   -1508
walk_pud_range                              3238       -   -3238
Total: Before=99449, After=91699, chg -7.79%
$ objdump -S mm/vmscan.o | grep -A 20 "<try_to_inc_max_seq>:"
000000000000a350 <try_to_inc_max_seq>:
{
    a350: e8 00 00 00 00        call   a355 <try_to_inc_max_seq+0x5>
    a355: 55                    push   %rbp
    a356: 48 89 e5              mov    %rsp,%rbp
    a359: 41 57                push   %r15
    a35b: 41 56                push   %r14
    a35d: 41 55                push   %r13
    a35f: 41 54                push   %r12
    a361: 53                    push   %rbx
    a362: 48 83 ec 70          sub    $0x70,%rsp
    a366: 41 89 d4              mov    %edx,%r12d
    a369: 49 89 f6              mov    %rsi,%r14
Quoted 8 lines
    a36c: 49 89 ff              mov    %rdi,%r15
spin_lock_irq(&lruvec->lru_lock);
    a36f: 48 8d 5f 50          lea    0x50(%rdi),%rbx
    a373: 48 89 df              mov    %rbx,%rdi
    a376: e8 00 00 00 00        call   a37b <try_to_inc_max_seq+0x2b>
success = max_seq == lrugen->max_seq;
    a37b: 49 8b 87 88 00 00 00 mov    0x88(%r15),%rax
    a382: 4c 39 f0              cmp    %r14,%rax

For the below diff:

@@ -4497,14 +4547,16 @@ static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long max_seq,
struct lru_gen_mm_walk *walk;
struct mm_struct *mm = NULL;
struct lru_gen_folio *lrugen = &lruvec->lrugen;
+ struct lru_gen_mm_state *mm_state = get_mm_state(lruvec);

VM_WARN_ON_ONCE(max_seq > READ_ONCE(lrugen->max_seq));

+ if (!mm_state)
+ return inc_max_seq(lruvec, max_seq, can_swap, force_scan);
+
/* see the comment in iterate_mm_list() */
- if (max_seq <= READ_ONCE(lruvec->mm_state.seq)) {
- success = false;
- goto done;
- }
+ if (max_seq <= READ_ONCE(mm_state->seq))
+ return false;

/*
* If the hardware doesn't automatically set the accessed bit, fallback
@@ -4534,8 +4586,10 @@ static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long max_seq,
walk_mm(lruvec, mm, walk);
} while (mm);
done:
- if (success)
- inc_max_seq(lruvec, can_swap, force_scan);
+ if (success) {
+ success = inc_max_seq(lruvec, max_seq, can_swap, force_scan);
+ WARN_ON_ONCE(!success);
+ }

return success;
}
@

We did discuss a possible race that can happen if we allow multiple callers hit inc_max_seq at the same time. inc_max_seq drop the lru_lock and restart the loop at the previous value of type. ie. if we want to do the above we might also need the below?

modified mm/vmscan.c
@@ -4368,6 +4368,7 @@ void inc_max_seq(struct lruvec *lruvec, bool can_swap, bool force_scan)
int type, zone;
struct lru_gen_struct *lrugen = &lruvec->lrugen;

+retry:
spin_lock_irq(&lruvec->lru_lock);

VM_WARN_ON_ONCE(!seq_is_valid(lruvec));
@@ -4381,7 +4382,7 @@ void inc_max_seq(struct lruvec *lruvec, bool can_swap, bool force_scan)
while (!inc_min_seq(lruvec, type, can_swap)) {
spin_unlock_irq(&lruvec->lru_lock);
cond_resched();
- spin_lock_irq(&lruvec->lru_lock);
+ goto retry;
}
}

I also found that allowing only one cpu to increment max seq value and making other request with the same max_seq return false is also useful in performance runs. ie, we need an equivalent of this?

+ if (max_seq <= READ_ONCE(mm_state->seq))
+ return false;

#8 lore raw
Quoted 167 lines · Aneesh Kumar K V
Quoted 118 lines · Yu Zhao
Quoted 13 lines · Aneesh Kumar K.V

This patchset avoids building changes added by commit bd74fdaea146 ("mm:
multi-gen LRU: support page table walks") on platforms that don't support hardware atomic updates of access bits.

Aneesh Kumar K.V (5):
mm/mglru: Create a new helper iterate_mm_list_walk mm/mglru: Move Bloom filter code around
mm/mglru: Move code around to make future patch easy mm/mglru: move iterate_mm_list_walk Helper mm/mglru: Don't build multi-gen LRU page table walk code on
architecture not supported

Quoted 10 lines
 arch/Kconfig               |   3 +
 arch/arm64/Kconfig         |   1 +
 arch/x86/Kconfig           |   1 +
 include/linux/memcontrol.h |   2 +-
 include/linux/mm_types.h   |  10 +-
 include/linux/mmzone.h     |  12 +-
 kernel/fork.c              |   2 +-
 mm/memcontrol.c            |   2 +-
 mm/vmscan.c                | 955 +++++++++++++++++++------------------
 9 files changed, 528 insertions(+), 460 deletions(-)

1. There is no need for a new Kconfig -- the condition is simply defined(CONFIG_LRU_GEN) && !defined(arch_has_hw_pte_young)

2. The best practice to disable static functions is not by macros but:

    static int const_cond(void)
    {
        return 1;
    }

    int main(void)
    {
        int a = const_cond();

        if (a)
                return 0;

        /* the compiler doesn't generate code for static funcs below */
        static_func_1();
        ...
        static_func_N();

LTO also optimizes external functions. But not everyone uses it. So we still need macros for them, and of course data structures.

3. In 4/5, you have:

    @@ -461,6 +461,7 @@ enum {
     struct lru_gen_mm_state {
        /* set to max_seq after each iteration */
        unsigned long seq;
    +#ifdef CONFIG_LRU_TASK_PAGE_AGING
        /* where the current iteration continues after */
        struct list_head *head;
        /* where the last iteration ended before */
    @@ -469,6 +470,11 @@ struct lru_gen_mm_state {
        unsigned long *filters[NR_BLOOM_FILTERS];
        /* the mm stats for debugging */
        unsigned long stats[NR_HIST_GENS][NR_MM_STATS];
    +#else
    +   /* protect the seq update above */
    +   /* May be we can use lruvec->lock?  */
    +   spinlock_t lock;
    +#endif
     };

The answer is yes, and not only that, we don't need lru_gen_mm_state at all.

I'm attaching a patch that fixes all above. If you want to post it, please feel free -- fully test it please, since I didn't. Otherwise I can ask TJ to help make this work for you.

$ git diff --stat
 include/linux/memcontrol.h |   2 +-
 include/linux/mm_types.h   |  12 +-
 include/linux/mmzone.h     |   2 +
 kernel/bounds.c            |   6 +-
 kernel/fork.c              |   2 +-
 mm/vmscan.c                | 169 +++++++++++++++++++--------
 6 files changed, 137 insertions(+), 56 deletions(-)

On x86:

$ ./scripts/bloat-o-meter mm/vmscan.o.old mm/vmscan.o
add/remove: 24/34 grow/shrink: 2/7 up/down: 966/-8716 (-7750)
Function                                     old     new   delta
...
should_skip_vma                              206       -    -206
get_pte_pfn                                  261       -    -261
lru_gen_add_mm                               323       -    -323
lru_gen_seq_show                            1710    1370    -340
lru_gen_del_mm                               432       -    -432
reset_batch_size                             572       -    -572
try_to_inc_max_seq                          2947    1635   -1312
walk_pmd_range_locked                       1508       -   -1508
walk_pud_range                              3238       -   -3238
Total: Before=99449, After=91699, chg -7.79%
$ objdump -S mm/vmscan.o | grep -A 20 "<try_to_inc_max_seq>:"
000000000000a350 <try_to_inc_max_seq>:
{
    a350: e8 00 00 00 00        call   a355 <try_to_inc_max_seq+0x5>
    a355: 55                    push   %rbp
    a356: 48 89 e5              mov    %rsp,%rbp
    a359: 41 57                push   %r15
    a35b: 41 56                push   %r14
    a35d: 41 55                push   %r13
    a35f: 41 54                push   %r12
    a361: 53                    push   %rbx
    a362: 48 83 ec 70          sub    $0x70,%rsp
    a366: 41 89 d4              mov    %edx,%r12d
    a369: 49 89 f6              mov    %rsi,%r14
Quoted 8 lines
    a36c: 49 89 ff              mov    %rdi,%r15
spin_lock_irq(&lruvec->lru_lock);
    a36f: 48 8d 5f 50          lea    0x50(%rdi),%rbx
    a373: 48 89 df              mov    %rbx,%rdi
    a376: e8 00 00 00 00        call   a37b <try_to_inc_max_seq+0x2b>
success = max_seq == lrugen->max_seq;
    a37b: 49 8b 87 88 00 00 00 mov    0x88(%r15),%rax
    a382: 4c 39 f0              cmp    %r14,%rax

For the below diff:

mm/vmscan.c +10 -6
@@ -4497,14 +4547,16 @@ static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long max_seq,
        struct lru_gen_mm_walk *walk;
        struct mm_struct *mm = NULL;
        struct lru_gen_folio *lrugen = &lruvec->lrugen;
+       struct lru_gen_mm_state *mm_state = get_mm_state(lruvec);
 
        VM_WARN_ON_ONCE(max_seq > READ_ONCE(lrugen->max_seq));
 
+       if (!mm_state)
+               return inc_max_seq(lruvec, max_seq, can_swap, force_scan);
+
        /* see the comment in iterate_mm_list() */
-       if (max_seq <= READ_ONCE(lruvec->mm_state.seq)) {
-               success = false;
-               goto done;
-       }
+       if (max_seq <= READ_ONCE(mm_state->seq))
+               return false;
 
        /*
         * If the hardware doesn't automatically set the accessed bit, fallback
@@ -4534,8 +4586,10 @@ static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long max_seq,
                        walk_mm(lruvec, mm, walk);
        } while (mm);
 done:
-       if (success)
-               inc_max_seq(lruvec, can_swap, force_scan);
+       if (success) {
+               success = inc_max_seq(lruvec, max_seq, can_swap, force_scan);
+               WARN_ON_ONCE(!success);
+       }
 
        return success;
 }

@

Quoted 3 lines

We did discuss a possible race that can happen if we allow multiple callers hit inc_max_seq at the same time. inc_max_seq drop the lru_lock and restart the loop at the previous value of type. ie. if we want to do the above we might also need the below?

Yes, you are right.

In fact, there is an existing bug here: even though max_seq can't change without the patch, min_seq still can, and if it does, the initial condition for inc_min_seq() to keep looping, i.e., nr_gens at max, doesn't hold anymore.

        for (type = ANON_AND_FILE - 1; type >= 0; type--) {
                if (get_nr_gens(lruvec, type) != MAX_NR_GENS)
                        continue;

This only affects the debugfs interface (force_scan=true), which is probably why it wasn't never reported.

Quoted 22 lines

modified mm/vmscan.c

(quoted diff) +2 -1
@@ -4368,6 +4368,7 @@ void inc_max_seq(struct lruvec *lruvec, bool can_swap, bool force_scan)
        int type, zone;
        struct lru_gen_struct *lrugen = &lruvec->lrugen;
 
+retry:
        spin_lock_irq(&lruvec->lru_lock);
 
        VM_WARN_ON_ONCE(!seq_is_valid(lruvec));
@@ -4381,7 +4382,7 @@ void inc_max_seq(struct lruvec *lruvec, bool can_swap, bool force_scan)
                while (!inc_min_seq(lruvec, type, can_swap)) {
                        spin_unlock_irq(&lruvec->lru_lock);
                        cond_resched();
-                       spin_lock_irq(&lruvec->lru_lock);
+                       goto retry;
                }
        }
 

I also found that allowing only one cpu to increment max seq value and making other request with the same max_seq return false is also useful in performance runs. ie, we need an equivalent of this?

mm/vmscan.c static bool try_to_inc_max_seq(struct lruvec *lruvec, unsigned long max_seq, · line 4559

Quoted 2 lines
mm/vmscan.c +2 -0
+       if (max_seq <= READ_ONCE(mm_state->seq))
+               return false;

Yes, but the condition should be "<" -- ">" is a bug, which was asserted in try_to_in_max_seq().