Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] mm: refactor zonelist constructors and iterators
@ 2026-09-02 21:58 Gregory Price
  2026-09-02 21:58 ` [PATCH 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
  2026-09-02 21:58 ` [PATCH 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
  0 siblings, 2 replies; 8+ messages in thread
From: Gregory Price @ 2026-09-02 21:58 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, brendan.jackman, hannes, ziy

find_next_best_node() picks the next-closest node when building a
fallback list, and hardcodes N_MEMORY as the set it picks from.

Refactor it into find_next_best_node_in(), which takes the candidate
set explicitly.

This makes the existing behaviour explicit at both mm/memory-tiers.c
call sites - they select demotion targets in fallback order from
N_MEMORY - and lets callers narrow that set.

Then extract the per-node construction loop out of build_zonelists()
into build_node_zonelist(), parameterised on the candidate nodemask
and on the destination zonelist index.

Together these allow a zonelist to be built over a candidate set other
than N_MEMORY, into a zonelist other than FALLBACK, and iterated in
fallback order over a caller-defined subset.

These are prerequisites for generating a private node zonelist (nodes
unreachable by default), but are otherwise general improvements to
the existing interfaces so I'm proposing them separately.

No functional change intended - purely refactor commits.

Verified on x86_64:

  - find_next_best_node_in() compiles to the same 319 bytes as
    find_next_best_node().
  - Both mm/memory-tiers.c callers grow a single instruction - the
    added argument.
  - Boot-time fallback orders are identical on a 4-node guest with an
    asymmetric distance matrix.

Gregory Price (2):
  mm: refactor find_next_best_node to find_next_best_node_in
  mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()

 mm/internal.h     |  6 ++++--
 mm/memory-tiers.c |  7 +++---
 mm/page_alloc.c   | 55 +++++++++++++++++++++++++++++++----------------
 3 files changed, 45 insertions(+), 23 deletions(-)

-- 
2.53.0-Meta



^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/2] mm: refactor find_next_best_node to find_next_best_node_in
  2026-09-02 21:58 [PATCH 0/2] mm: refactor zonelist constructors and iterators Gregory Price
@ 2026-09-02 21:58 ` Gregory Price
  2026-09-02 23:48   ` Gregory Price
  2026-09-03 15:15   ` Vlastimil Babka (SUSE)
  2026-09-02 21:58 ` [PATCH 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
  1 sibling, 2 replies; 8+ messages in thread
From: Gregory Price @ 2026-09-02 21:58 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, brendan.jackman, hannes, ziy

find_next_best_node() picks the next-closest node for a fallback list
from the full N_MEMORY set. Refactor it into find_next_best_node_in(),
which takes an explicit candidates nodemask.

This enables building fallback lists with non-N_MEMORY candidates.

No functional change: every caller still selects from N_MEMORY.

Signed-off-by: Gregory Price <gourry@gourry.net>
---
 mm/internal.h     |  6 ++++--
 mm/memory-tiers.c |  7 ++++---
 mm/page_alloc.c   | 13 ++++++++-----
 3 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/mm/internal.h b/mm/internal.h
index e16f1250b25c8..18d041ff5c52c 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1130,7 +1130,8 @@ extern int node_reclaim_mode;
 
 extern unsigned long node_reclaim(struct pglist_data *pgdat,
 				  gfp_t gfp_mask, unsigned int order);
-extern int find_next_best_node(int node, nodemask_t *used_node_mask);
+extern int find_next_best_node_in(int node, nodemask_t *used_node_mask,
+				  const nodemask_t *candidates);
 #else
 #define node_reclaim_mode 0
 
@@ -1139,7 +1140,8 @@ static inline unsigned long node_reclaim(struct pglist_data *pgdat,
 {
 	return 0;
 }
-static inline int find_next_best_node(int node, nodemask_t *used_node_mask)
+static inline int find_next_best_node_in(int node, nodemask_t *used_node_mask,
+					 const nodemask_t *candidates)
 {
 	return NUMA_NO_NODE;
 }
diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c
index 54851d8a195b0..25e121851b586 100644
--- a/mm/memory-tiers.c
+++ b/mm/memory-tiers.c
@@ -370,7 +370,7 @@ int next_demotion_node(int node, const nodemask_t *allowed_mask)
 	 * closest demotion target.
 	 */
 	nodes_complement(mask, *allowed_mask);
-	return find_next_best_node(node, &mask);
+	return find_next_best_node_in(node, &mask, &node_states[N_MEMORY]);
 }
 
 static void disable_all_demotion_targets(void)
@@ -450,7 +450,7 @@ static void establish_demotion_targets(void)
 		memtier = list_next_entry(memtier, list);
 		tier_nodes = get_memtier_nodemask(memtier);
 		/*
-		 * find_next_best_node, use 'used' nodemask as a skip list.
+		 * find_next_best_node_in, use 'used' nodemask as a skip list.
 		 * Add all memory nodes except the selected memory tier
 		 * nodelist to skip list so that we find the best node from the
 		 * memtier nodelist.
@@ -463,7 +463,8 @@ static void establish_demotion_targets(void)
 		 * in the preferred mask when allocating pages during demotion.
 		 */
 		do {
-			target = find_next_best_node(node, &tier_nodes);
+			target = find_next_best_node_in(node, &tier_nodes,
+							&node_states[N_MEMORY]);
 			if (target == NUMA_NO_NODE)
 				break;
 
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index c4dc61ec663ee..4dde1cbe2fd43 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -5779,9 +5779,10 @@ static int numa_zonelist_order_handler(const struct ctl_table *table, int write,
 static int node_load[MAX_NUMNODES];
 
 /**
- * find_next_best_node - find the next node that should appear in a given node's fallback list
+ * find_next_best_node_in - find the next node that should appear in a given node's fallback list
  * @node: node whose fallback list we're appending
  * @used_node_mask: nodemask_t of already used nodes
+ * @candidates: nodemask_t of nodes eligible for selection
  *
  * We use a number of factors to determine which is the next node that should
  * appear on a given node's fallback list.  The node should not have appeared
@@ -5793,7 +5794,8 @@ static int node_load[MAX_NUMNODES];
  *
  * Return: node id of the found node or %NUMA_NO_NODE if no node is found.
  */
-int find_next_best_node(int node, nodemask_t *used_node_mask)
+int find_next_best_node_in(int node, nodemask_t *used_node_mask,
+			   const nodemask_t *candidates)
 {
 	int n, val;
 	int min_val = INT_MAX;
@@ -5803,12 +5805,12 @@ int find_next_best_node(int node, nodemask_t *used_node_mask)
 	 * Use the local node if we haven't already, but for memoryless local
 	 * node, we should skip it and fall back to other nodes.
 	 */
-	if (!node_isset(node, *used_node_mask) && node_state(node, N_MEMORY)) {
+	if (!node_isset(node, *used_node_mask) && node_isset(node, *candidates)) {
 		node_set(node, *used_node_mask);
 		return node;
 	}
 
-	for_each_node_state(n, N_MEMORY) {
+	for_each_node_mask(n, *candidates) {
 
 		/* Don't want a node to appear more than once */
 		if (node_isset(n, *used_node_mask))
@@ -5893,7 +5895,8 @@ static void build_zonelists(pg_data_t *pgdat)
 	prev_node = local_node;
 
 	memset(node_order, 0, sizeof(node_order));
-	while ((node = find_next_best_node(local_node, &used_mask)) >= 0) {
+	while ((node = find_next_best_node_in(local_node, &used_mask,
+					      &node_states[N_MEMORY])) >= 0) {
 		/*
 		 * We don't want to pressure a particular node.
 		 * So adding penalty to the first node in same
-- 
2.53.0-Meta



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-02 21:58 [PATCH 0/2] mm: refactor zonelist constructors and iterators Gregory Price
  2026-09-02 21:58 ` [PATCH 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
@ 2026-09-02 21:58 ` Gregory Price
  2026-09-03 15:33   ` Vlastimil Babka (SUSE)
  1 sibling, 1 reply; 8+ messages in thread
From: Gregory Price @ 2026-09-02 21:58 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, brendan.jackman, hannes, ziy

Extract per-node fallback-list construction into build_node_zonelist().

This lets us build new zonelists from candidate nodemasks instead of
just the default N_MEMORY node state list.

No functional change: build_zonelists() builds the same FALLBACK list over
N_MEMORY with node_load updates as before.

Signed-off-by: Gregory Price <gourry@gourry.net>
---
 mm/page_alloc.c | 44 ++++++++++++++++++++++++++++++--------------
 1 file changed, 30 insertions(+), 14 deletions(-)

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 4dde1cbe2fd43..e1d7c8b221d0d 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -5849,12 +5849,12 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
  * DMA zone, if any--but risks exhausting DMA zone.
  */
 static void build_zonelists_in_node_order(pg_data_t *pgdat, int *node_order,
-		unsigned nr_nodes)
+		unsigned int nr_nodes, int zlidx)
 {
 	struct zoneref *zonerefs;
 	int i;
 
-	zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
+	zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
 
 	for (i = 0; i < nr_nodes; i++) {
 		int nr_zones;
@@ -5883,26 +5883,28 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
 	zonerefs->zone_idx = 0;
 }
 
-static void build_zonelists(pg_data_t *pgdat)
+/*
+ * Build one node-ordered fallback list from a candidate nodemask.
+ * update_load round-robins node_load across equidistant nodes.
+ */
+static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
+				int zlidx, bool update_load,
+				int *node_order, int *nr)
 {
-	static int node_order[MAX_NUMNODES];
-	int node, nr_nodes = 0;
 	nodemask_t used_mask = NODE_MASK_NONE;
-	int local_node, prev_node;
-
-	/* NUMA-aware ordering of nodes */
-	local_node = pgdat->node_id;
-	prev_node = local_node;
+	int local_node = pgdat->node_id;
+	int prev_node = local_node;
+	int node, nr_nodes = 0;
 
-	memset(node_order, 0, sizeof(node_order));
 	while ((node = find_next_best_node_in(local_node, &used_mask,
-					      &node_states[N_MEMORY])) >= 0) {
+					      candidates)) >= 0) {
 		/*
 		 * We don't want to pressure a particular node.
 		 * So adding penalty to the first node in same
 		 * distance group to make it round-robin.
 		 */
-		if (node_distance(local_node, node) !=
+		if (update_load &&
+		    node_distance(local_node, node) !=
 		    node_distance(local_node, prev_node))
 			node_load[node] += 1;
 
@@ -5910,8 +5912,22 @@ static void build_zonelists(pg_data_t *pgdat)
 		prev_node = node;
 	}
 
-	build_zonelists_in_node_order(pgdat, node_order, nr_nodes);
+	build_zonelists_in_node_order(pgdat, node_order, nr_nodes, zlidx);
+	*nr = nr_nodes;
+}
+
+static void build_zonelists(pg_data_t *pgdat)
+{
+	static int node_order[MAX_NUMNODES];
+	int local_node = pgdat->node_id;
+	int node, nr_nodes = 0;
+
+	memset(node_order, 0, sizeof(node_order));
+
+	build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK,
+			    true, node_order, &nr_nodes);
 	build_thisnode_zonelists(pgdat);
+
 	pr_info("Fallback order for Node %d: ", local_node);
 	for (node = 0; node < nr_nodes; node++)
 		pr_cont("%d ", node_order[node]);
-- 
2.53.0-Meta



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] mm: refactor find_next_best_node to find_next_best_node_in
  2026-09-02 21:58 ` [PATCH 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
@ 2026-09-02 23:48   ` Gregory Price
  2026-09-03 15:15   ` Vlastimil Babka (SUSE)
  1 sibling, 0 replies; 8+ messages in thread
From: Gregory Price @ 2026-09-02 23:48 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, brendan.jackman, hannes, ziy, balbirs

On Wed, Sep 02, 2026 at 05:58:52PM -0400, Gregory Price wrote:
> find_next_best_node() picks the next-closest node for a fallback list
> from the full N_MEMORY set. Refactor it into find_next_best_node_in(),
> which takes an explicit candidates nodemask.
> 
> This enables building fallback lists with non-N_MEMORY candidates.
> 
> No functional change: every caller still selects from N_MEMORY.
> 
> Signed-off-by: Gregory Price <gourry@gourry.net>

Just realized this should carry an Ack from Balbir

Acked-by: Balbir Singh <balbirs@nvidia.com>

see - https://lore.kernel.org/linux-mm/62bf02dc-dab1-41bf-8204-eb3566628b93@nvidia.com/



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] mm: refactor find_next_best_node to find_next_best_node_in
  2026-09-02 21:58 ` [PATCH 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
  2026-09-02 23:48   ` Gregory Price
@ 2026-09-03 15:15   ` Vlastimil Babka (SUSE)
  2026-09-03 16:46     ` Gregory Price
  1 sibling, 1 reply; 8+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-09-03 15:15 UTC (permalink / raw)
  To: Gregory Price, linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, rppt, surenb,
	mhocko, brendan.jackman, hannes, ziy

On 9/2/26 23:58, Gregory Price wrote:
> find_next_best_node() picks the next-closest node for a fallback list
> from the full N_MEMORY set. Refactor it into find_next_best_node_in(),
> which takes an explicit candidates nodemask.
> 
> This enables building fallback lists with non-N_MEMORY candidates.
> 
> No functional change: every caller still selects from N_MEMORY.
> 
> Signed-off-by: Gregory Price <gourry@gourry.net>

Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>

Nit:

> ---
>  mm/internal.h     |  6 ++++--
>  mm/memory-tiers.c |  7 ++++---
>  mm/page_alloc.c   | 13 ++++++++-----
>  3 files changed, 16 insertions(+), 10 deletions(-)
> 
> diff --git a/mm/internal.h b/mm/internal.h
> index e16f1250b25c8..18d041ff5c52c 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -1130,7 +1130,8 @@ extern int node_reclaim_mode;
>  
>  extern unsigned long node_reclaim(struct pglist_data *pgdat,
>  				  gfp_t gfp_mask, unsigned int order);
> -extern int find_next_best_node(int node, nodemask_t *used_node_mask);
> +extern int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> +				  const nodemask_t *candidates);

Drop the extern while touching a line.

>  #else
>  #define node_reclaim_mode 0
>  
> @@ -1139,7 +1140,8 @@ static inline unsigned long node_reclaim(struct pglist_data *pgdat,
>  {
>  	return 0;
>  }
> -static inline int find_next_best_node(int node, nodemask_t *used_node_mask)
> +static inline int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> +					 const nodemask_t *candidates)
>  {
>  	return NUMA_NO_NODE;
>  }
> diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c
> index 54851d8a195b0..25e121851b586 100644
> --- a/mm/memory-tiers.c
> +++ b/mm/memory-tiers.c
> @@ -370,7 +370,7 @@ int next_demotion_node(int node, const nodemask_t *allowed_mask)
>  	 * closest demotion target.
>  	 */
>  	nodes_complement(mask, *allowed_mask);
> -	return find_next_best_node(node, &mask);
> +	return find_next_best_node_in(node, &mask, &node_states[N_MEMORY]);
>  }
>  
>  static void disable_all_demotion_targets(void)
> @@ -450,7 +450,7 @@ static void establish_demotion_targets(void)
>  		memtier = list_next_entry(memtier, list);
>  		tier_nodes = get_memtier_nodemask(memtier);
>  		/*
> -		 * find_next_best_node, use 'used' nodemask as a skip list.
> +		 * find_next_best_node_in, use 'used' nodemask as a skip list.
>  		 * Add all memory nodes except the selected memory tier
>  		 * nodelist to skip list so that we find the best node from the
>  		 * memtier nodelist.
> @@ -463,7 +463,8 @@ static void establish_demotion_targets(void)
>  		 * in the preferred mask when allocating pages during demotion.
>  		 */
>  		do {
> -			target = find_next_best_node(node, &tier_nodes);
> +			target = find_next_best_node_in(node, &tier_nodes,
> +							&node_states[N_MEMORY]);
>  			if (target == NUMA_NO_NODE)
>  				break;
>  
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index c4dc61ec663ee..4dde1cbe2fd43 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -5779,9 +5779,10 @@ static int numa_zonelist_order_handler(const struct ctl_table *table, int write,
>  static int node_load[MAX_NUMNODES];
>  
>  /**
> - * find_next_best_node - find the next node that should appear in a given node's fallback list
> + * find_next_best_node_in - find the next node that should appear in a given node's fallback list
>   * @node: node whose fallback list we're appending
>   * @used_node_mask: nodemask_t of already used nodes
> + * @candidates: nodemask_t of nodes eligible for selection
>   *
>   * We use a number of factors to determine which is the next node that should
>   * appear on a given node's fallback list.  The node should not have appeared
> @@ -5793,7 +5794,8 @@ static int node_load[MAX_NUMNODES];
>   *
>   * Return: node id of the found node or %NUMA_NO_NODE if no node is found.
>   */
> -int find_next_best_node(int node, nodemask_t *used_node_mask)
> +int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> +			   const nodemask_t *candidates)
>  {
>  	int n, val;
>  	int min_val = INT_MAX;
> @@ -5803,12 +5805,12 @@ int find_next_best_node(int node, nodemask_t *used_node_mask)
>  	 * Use the local node if we haven't already, but for memoryless local
>  	 * node, we should skip it and fall back to other nodes.
>  	 */
> -	if (!node_isset(node, *used_node_mask) && node_state(node, N_MEMORY)) {
> +	if (!node_isset(node, *used_node_mask) && node_isset(node, *candidates)) {
>  		node_set(node, *used_node_mask);
>  		return node;
>  	}
>  
> -	for_each_node_state(n, N_MEMORY) {
> +	for_each_node_mask(n, *candidates) {
>  
>  		/* Don't want a node to appear more than once */
>  		if (node_isset(n, *used_node_mask))
> @@ -5893,7 +5895,8 @@ static void build_zonelists(pg_data_t *pgdat)
>  	prev_node = local_node;
>  
>  	memset(node_order, 0, sizeof(node_order));
> -	while ((node = find_next_best_node(local_node, &used_mask)) >= 0) {
> +	while ((node = find_next_best_node_in(local_node, &used_mask,
> +					      &node_states[N_MEMORY])) >= 0) {
>  		/*
>  		 * We don't want to pressure a particular node.
>  		 * So adding penalty to the first node in same



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-02 21:58 ` [PATCH 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
@ 2026-09-03 15:33   ` Vlastimil Babka (SUSE)
  2026-09-03 16:02     ` Gregory Price
  0 siblings, 1 reply; 8+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-09-03 15:33 UTC (permalink / raw)
  To: Gregory Price, linux-mm
  Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, rppt, surenb,
	mhocko, brendan.jackman, hannes, ziy

On 9/2/26 23:58, Gregory Price wrote:
> Extract per-node fallback-list construction into build_node_zonelist().
> 
> This lets us build new zonelists from candidate nodemasks instead of
> just the default N_MEMORY node state list.
> 
> No functional change: build_zonelists() builds the same FALLBACK list over
> N_MEMORY with node_load updates as before.
> 
> Signed-off-by: Gregory Price <gourry@gourry.net>

LGTM, but, while we're at it, could we just do the pr_cont() printing in
build_node_zonelist() itself (maybe behind a flag if you don't want to print
from future new caller) so it doesn't need to pass nr_nodes back to
build_zonelists(). Then also the node_order array could live in
build_node_zonelist() itself?

Actually I wonder if we could get ride of the node_order array completely.
It would mean the loop processing in build_zonelists_in_node_order() would
have to be done piece-meal in build_node_zonelist() itself. But seems
feasible? Depends on how you intend to reuse/extend the new functions later,
I guess...

> ---
>  mm/page_alloc.c | 44 ++++++++++++++++++++++++++++++--------------
>  1 file changed, 30 insertions(+), 14 deletions(-)
> 
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 4dde1cbe2fd43..e1d7c8b221d0d 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -5849,12 +5849,12 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
>   * DMA zone, if any--but risks exhausting DMA zone.
>   */
>  static void build_zonelists_in_node_order(pg_data_t *pgdat, int *node_order,
> -		unsigned nr_nodes)
> +		unsigned int nr_nodes, int zlidx)
>  {
>  	struct zoneref *zonerefs;
>  	int i;
>  
> -	zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
> +	zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
>  
>  	for (i = 0; i < nr_nodes; i++) {
>  		int nr_zones;
> @@ -5883,26 +5883,28 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
>  	zonerefs->zone_idx = 0;
>  }
>  
> -static void build_zonelists(pg_data_t *pgdat)
> +/*
> + * Build one node-ordered fallback list from a candidate nodemask.
> + * update_load round-robins node_load across equidistant nodes.
> + */
> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
> +				int zlidx, bool update_load,
> +				int *node_order, int *nr)
>  {
> -	static int node_order[MAX_NUMNODES];
> -	int node, nr_nodes = 0;
>  	nodemask_t used_mask = NODE_MASK_NONE;
> -	int local_node, prev_node;
> -
> -	/* NUMA-aware ordering of nodes */
> -	local_node = pgdat->node_id;
> -	prev_node = local_node;
> +	int local_node = pgdat->node_id;
> +	int prev_node = local_node;
> +	int node, nr_nodes = 0;
>  
> -	memset(node_order, 0, sizeof(node_order));
>  	while ((node = find_next_best_node_in(local_node, &used_mask,
> -					      &node_states[N_MEMORY])) >= 0) {
> +					      candidates)) >= 0) {
>  		/*
>  		 * We don't want to pressure a particular node.
>  		 * So adding penalty to the first node in same
>  		 * distance group to make it round-robin.
>  		 */
> -		if (node_distance(local_node, node) !=
> +		if (update_load &&
> +		    node_distance(local_node, node) !=
>  		    node_distance(local_node, prev_node))
>  			node_load[node] += 1;
>  
> @@ -5910,8 +5912,22 @@ static void build_zonelists(pg_data_t *pgdat)
>  		prev_node = node;
>  	}
>  
> -	build_zonelists_in_node_order(pgdat, node_order, nr_nodes);
> +	build_zonelists_in_node_order(pgdat, node_order, nr_nodes, zlidx);
> +	*nr = nr_nodes;
> +}
> +
> +static void build_zonelists(pg_data_t *pgdat)
> +{
> +	static int node_order[MAX_NUMNODES];
> +	int local_node = pgdat->node_id;
> +	int node, nr_nodes = 0;
> +
> +	memset(node_order, 0, sizeof(node_order));
> +
> +	build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK,
> +			    true, node_order, &nr_nodes);
>  	build_thisnode_zonelists(pgdat);
> +
>  	pr_info("Fallback order for Node %d: ", local_node);
>  	for (node = 0; node < nr_nodes; node++)
>  		pr_cont("%d ", node_order[node]);



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
  2026-09-03 15:33   ` Vlastimil Babka (SUSE)
@ 2026-09-03 16:02     ` Gregory Price
  0 siblings, 0 replies; 8+ messages in thread
From: Gregory Price @ 2026-09-03 16:02 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE)
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam, rppt,
	surenb, mhocko, brendan.jackman, hannes, ziy

On Thu, Sep 03, 2026 at 05:33:42PM +0200, Vlastimil Babka (SUSE) wrote:
> On 9/2/26 23:58, Gregory Price wrote:
> > Extract per-node fallback-list construction into build_node_zonelist().
> > 
> > This lets us build new zonelists from candidate nodemasks instead of
> > just the default N_MEMORY node state list.
> > 
> > No functional change: build_zonelists() builds the same FALLBACK list over
> > N_MEMORY with node_load updates as before.
> > 
> > Signed-off-by: Gregory Price <gourry@gourry.net>
> 
> LGTM, but, while we're at it, could we just do the pr_cont() printing in
> build_node_zonelist() itself (maybe behind a flag if you don't want to print
> from future new caller) so it doesn't need to pass nr_nodes back to
> build_zonelists(). Then also the node_order array could live in
> build_node_zonelist() itself?
>

Seems trivial enough, will let this sit for a minute and spin a v2.

> Actually I wonder if we could get ride of the node_order array completely.
> It would mean the loop processing in build_zonelists_in_node_order() would
> have to be done piece-meal in build_node_zonelist() itself. But seems
> feasible? Depends on how you intend to reuse/extend the new functions later,
> I guess...
> 

I can take a look.  We've been testing a couple different things, will
see how well things generalize (private node fallback lists, inverted
fallback lists for proactive reclaim node ordering, some other stuff).

~Gregory


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] mm: refactor find_next_best_node to find_next_best_node_in
  2026-09-03 15:15   ` Vlastimil Babka (SUSE)
@ 2026-09-03 16:46     ` Gregory Price
  0 siblings, 0 replies; 8+ messages in thread
From: Gregory Price @ 2026-09-03 16:46 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE)
  Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam, rppt,
	surenb, mhocko, brendan.jackman, hannes, ziy

On Thu, Sep 03, 2026 at 05:15:23PM +0200, Vlastimil Babka (SUSE) wrote:
> On 9/2/26 23:58, Gregory Price wrote:
> > find_next_best_node() picks the next-closest node for a fallback list
> > from the full N_MEMORY set. Refactor it into find_next_best_node_in(),
> > which takes an explicit candidates nodemask.
> > 
> > This enables building fallback lists with non-N_MEMORY candidates.
> > 
> > No functional change: every caller still selects from N_MEMORY.
> > 
> > Signed-off-by: Gregory Price <gourry@gourry.net>
> 
> Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> 
> Nit:
> 
> > ---
> >  mm/internal.h     |  6 ++++--
> >  mm/memory-tiers.c |  7 ++++---
> >  mm/page_alloc.c   | 13 ++++++++-----
> >  3 files changed, 16 insertions(+), 10 deletions(-)
> > 
> > diff --git a/mm/internal.h b/mm/internal.h
> > index e16f1250b25c8..18d041ff5c52c 100644
> > --- a/mm/internal.h
> > +++ b/mm/internal.h
> > @@ -1130,7 +1130,8 @@ extern int node_reclaim_mode;
> >  
> >  extern unsigned long node_reclaim(struct pglist_data *pgdat,
> >  				  gfp_t gfp_mask, unsigned int order);
> > -extern int find_next_best_node(int node, nodemask_t *used_node_mask);
> > +extern int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> > +				  const nodemask_t *candidates);
> 
> Drop the extern while touching a line.
> 

ack.


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-09-03 16:46 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-02 21:58 [PATCH 0/2] mm: refactor zonelist constructors and iterators Gregory Price
2026-09-02 21:58 ` [PATCH 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
2026-09-02 23:48   ` Gregory Price
2026-09-03 15:15   ` Vlastimil Babka (SUSE)
2026-09-03 16:46     ` Gregory Price
2026-09-02 21:58 ` [PATCH 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
2026-09-03 15:33   ` Vlastimil Babka (SUSE)
2026-09-03 16:02     ` Gregory Price

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox