* [PATCH v2 0/2] mm: refactor zonelist constructors and iterators
@ 2026-09-12 3:04 Gregory Price
2026-09-12 3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
` (2 more replies)
0 siblings, 3 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-12 3:04 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(),i parameterised on the candidate nodemask
and 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 | 74 +++++++++++++++++++----------------------------
3 files changed, 38 insertions(+), 49 deletions(-)
---
v2:
- drop extern (Vlastimil)
- move printing into build_node_zonelist() (Vlastimil)
- get rid of node_order array (Vlastimil)
- drop update_load bool (will add back when relevant)
--
2.55.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in
2026-09-12 3:04 [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Gregory Price
@ 2026-09-12 3:04 ` Gregory Price
2026-09-14 15:00 ` Zi Yan
2026-09-18 20:42 ` David Hildenbrand (Arm)
2026-09-12 3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
2026-09-15 16:27 ` [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Zenghui Yu
2 siblings, 2 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-12 3:04 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, brendan.jackman, hannes, ziy, Balbir Singh
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.
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
Acked-by: Balbir Singh <balbirs@nvidia.com>
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 da14c56fb24e..1519dd21a900 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1133,7 +1133,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);
+int find_next_best_node_in(int node, nodemask_t *used_node_mask,
+ const nodemask_t *candidates);
#else
#define node_reclaim_mode 0
@@ -1142,7 +1143,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 54851d8a195b..25e121851b58 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 1cfbd0582a10..7efce139d562 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -5793,9 +5793,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
@@ -5807,7 +5808,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;
@@ -5817,12 +5819,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))
@@ -5907,7 +5909,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.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-12 3:04 [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Gregory Price
2026-09-12 3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
@ 2026-09-12 3:04 ` Gregory Price
2026-09-14 14:56 ` Vlastimil Babka (SUSE)
2026-09-21 2:49 ` Zi Yan
2026-09-15 16:27 ` [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Zenghui Yu
2 siblings, 2 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-12 3:04 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().
Build each selected node directly into the destination zonelist so no
intermediate node_order array or node count is needed. Print the fallback
order as each node is added.
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 and prints 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 | 63 ++++++++++++++++++-------------------------------
1 file changed, 23 insertions(+), 40 deletions(-)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 7efce139d562..d1888d5630e0 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -5857,31 +5857,6 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
}
-/*
- * Build zonelists ordered by node and zones within node.
- * This results in maximum locality--normal zone overflows into local
- * 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)
-{
- struct zoneref *zonerefs;
- int i;
-
- zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
-
- for (i = 0; i < nr_nodes; i++) {
- int nr_zones;
-
- pg_data_t *node = NODE_DATA(node_order[i]);
-
- nr_zones = build_zonerefs_node(node, zonerefs);
- zonerefs += nr_zones;
- }
- zonerefs->zone = NULL;
- zonerefs->zone_idx = 0;
-}
-
/*
* Build __GFP_THISNODE zonelists
*/
@@ -5897,20 +5872,24 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
zonerefs->zone_idx = 0;
}
-static void build_zonelists(pg_data_t *pgdat)
+/*
+ * Build one zonelist ordered by node and zones within node. This results in
+ * maximum locality--normal zone overflows into local DMA zone, if any--but
+ * risks exhausting DMA zone.
+ */
+static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
+ int zlidx)
{
- static int node_order[MAX_NUMNODES];
- int node, nr_nodes = 0;
+ struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
nodemask_t used_mask = NODE_MASK_NONE;
- int local_node, prev_node;
+ int local_node = pgdat->node_id;
+ int prev_node = local_node;
+ int node;
- /* NUMA-aware ordering of nodes */
- local_node = pgdat->node_id;
- prev_node = local_node;
+ pr_info("Fallback order for Node %d: ", local_node);
- 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
@@ -5920,18 +5899,22 @@ static void build_zonelists(pg_data_t *pgdat)
node_distance(local_node, prev_node))
node_load[node] += 1;
- node_order[nr_nodes++] = node;
+ zonerefs += build_zonerefs_node(NODE_DATA(node), zonerefs);
+ pr_cont("%d ", node);
prev_node = node;
}
- build_zonelists_in_node_order(pgdat, 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]);
+ zonerefs->zone = NULL;
+ zonerefs->zone_idx = 0;
pr_cont("\n");
}
+static void build_zonelists(pg_data_t *pgdat)
+{
+ build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
+ build_thisnode_zonelists(pgdat);
+}
+
#ifdef CONFIG_HAVE_MEMORYLESS_NODES
/*
* Return node id of node used for "local" allocations.
--
2.55.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-12 3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
@ 2026-09-14 14:56 ` Vlastimil Babka (SUSE)
2026-09-14 15:04 ` Gregory Price
2026-09-21 2:49 ` Zi Yan
1 sibling, 1 reply; 20+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-09-14 14:56 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/12/26 05:04, Gregory Price wrote:
> Extract per-node fallback-list construction into build_node_zonelist().
> Build each selected node directly into the destination zonelist so no
> intermediate node_order array or node count is needed. Print the fallback
> order as each node is added.
>
> 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 and prints the same FALLBACK
> list over N_MEMORY with node_load updates as before.
>
> Signed-off-by: Gregory Price <gourry@gourry.net>
Nice, glad this way of refactor was feasible.
Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> ---
> mm/page_alloc.c | 63 ++++++++++++++++++-------------------------------
> 1 file changed, 23 insertions(+), 40 deletions(-)
>
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 7efce139d562..d1888d5630e0 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -5857,31 +5857,6 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> }
>
>
> -/*
> - * Build zonelists ordered by node and zones within node.
> - * This results in maximum locality--normal zone overflows into local
> - * 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)
> -{
> - struct zoneref *zonerefs;
> - int i;
> -
> - zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
> -
> - for (i = 0; i < nr_nodes; i++) {
> - int nr_zones;
> -
> - pg_data_t *node = NODE_DATA(node_order[i]);
> -
> - nr_zones = build_zonerefs_node(node, zonerefs);
> - zonerefs += nr_zones;
> - }
> - zonerefs->zone = NULL;
> - zonerefs->zone_idx = 0;
> -}
> -
> /*
> * Build __GFP_THISNODE zonelists
> */
> @@ -5897,20 +5872,24 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
> zonerefs->zone_idx = 0;
> }
>
> -static void build_zonelists(pg_data_t *pgdat)
> +/*
> + * Build one zonelist ordered by node and zones within node. This results in
> + * maximum locality--normal zone overflows into local DMA zone, if any--but
> + * risks exhausting DMA zone.
> + */
> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
> + int zlidx)
> {
> - static int node_order[MAX_NUMNODES];
> - int node, nr_nodes = 0;
> + struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
> nodemask_t used_mask = NODE_MASK_NONE;
> - int local_node, prev_node;
> + int local_node = pgdat->node_id;
> + int prev_node = local_node;
> + int node;
>
> - /* NUMA-aware ordering of nodes */
> - local_node = pgdat->node_id;
> - prev_node = local_node;
> + pr_info("Fallback order for Node %d: ", local_node);
>
> - 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
> @@ -5920,18 +5899,22 @@ static void build_zonelists(pg_data_t *pgdat)
> node_distance(local_node, prev_node))
> node_load[node] += 1;
>
> - node_order[nr_nodes++] = node;
> + zonerefs += build_zonerefs_node(NODE_DATA(node), zonerefs);
> + pr_cont("%d ", node);
> prev_node = node;
> }
>
> - build_zonelists_in_node_order(pgdat, 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]);
> + zonerefs->zone = NULL;
> + zonerefs->zone_idx = 0;
> pr_cont("\n");
> }
>
> +static void build_zonelists(pg_data_t *pgdat)
> +{
> + build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
> + build_thisnode_zonelists(pgdat);
> +}
> +
> #ifdef CONFIG_HAVE_MEMORYLESS_NODES
> /*
> * Return node id of node used for "local" allocations.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in
2026-09-12 3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
@ 2026-09-14 15:00 ` Zi Yan
2026-09-18 20:42 ` David Hildenbrand (Arm)
1 sibling, 0 replies; 20+ messages in thread
From: Zi Yan @ 2026-09-14 15:00 UTC (permalink / raw)
To: Gregory Price
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes,
Balbir Singh
On 11 Sep 2026, at 23:04, 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.
>
> Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> Acked-by: Balbir Singh <balbirs@nvidia.com>
> 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(-)
>
LGTM.
Reviewed-by: Zi Yan <ziy@nvidia.com>
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-14 14:56 ` Vlastimil Babka (SUSE)
@ 2026-09-14 15:04 ` Gregory Price
0 siblings, 0 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-14 15:04 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 Mon, Sep 14, 2026 at 04:56:30PM +0200, Vlastimil Babka (SUSE) wrote:
> On 9/12/26 05:04, Gregory Price wrote:
> > Extract per-node fallback-list construction into build_node_zonelist().
> > Build each selected node directly into the destination zonelist so no
> > intermediate node_order array or node count is needed. Print the fallback
> > order as each node is added.
> >
> > 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 and prints the same FALLBACK
> > list over N_MEMORY with node_load updates as before.
> >
> > Signed-off-by: Gregory Price <gourry@gourry.net>
>
> Nice, glad this way of refactor was feasible.
>
Yup, came out much cleaner than i expected.
> Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
>
Thank you!
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 0/2] mm: refactor zonelist constructors and iterators
2026-09-12 3:04 [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Gregory Price
2026-09-12 3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
2026-09-12 3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
@ 2026-09-15 16:27 ` Zenghui Yu
2 siblings, 0 replies; 20+ messages in thread
From: Zenghui Yu @ 2026-09-15 16:27 UTC (permalink / raw)
To: Gregory Price
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes, ziy
On 9/12/26 11:04 AM, Gregory Price wrote:
> 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(),i parameterised on the candidate nodemask
> and 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 | 74 +++++++++++++++++++----------------------------
> 3 files changed, 38 insertions(+), 49 deletions(-)
Reviewed-by: Zenghui Yu (Huawei) <zenghui.yu@linux.dev>
Thanks,
Zenghui
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in
2026-09-12 3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
2026-09-14 15:00 ` Zi Yan
@ 2026-09-18 20:42 ` David Hildenbrand (Arm)
2026-09-18 21:49 ` Gregory Price
1 sibling, 1 reply; 20+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-18 20:42 UTC (permalink / raw)
To: Gregory Price, linux-mm
Cc: linux-kernel, kernel-team, akpm, ljs, liam, vbabka, rppt, surenb,
mhocko, brendan.jackman, hannes, ziy, Balbir Singh
On 9/12/26 05:04, 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.
>
> Reviewed-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> Acked-by: Balbir Singh <balbirs@nvidia.com>
> 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 da14c56fb24e..1519dd21a900 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -1133,7 +1133,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);
> +int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> + const nodemask_t *candidates);
Just curious, for your use case, wouldn't it be enough to pass N_MEMORY?
Or do we expect to pass other actual node masks that are not derived from
node_states?
--
Cheers,
David
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in
2026-09-18 20:42 ` David Hildenbrand (Arm)
@ 2026-09-18 21:49 ` Gregory Price
0 siblings, 0 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-18 21:49 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: linux-mm, linux-kernel, kernel-team, akpm, ljs, liam, vbabka,
rppt, surenb, mhocko, brendan.jackman, hannes, ziy, Balbir Singh
On Fri, Sep 18, 2026 at 10:42:12PM +0200, David Hildenbrand (Arm) wrote:
> > diff --git a/mm/internal.h b/mm/internal.h
> > index da14c56fb24e..1519dd21a900 100644
> > --- a/mm/internal.h
> > +++ b/mm/internal.h
> > @@ -1133,7 +1133,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);
> > +int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> > + const nodemask_t *candidates);
>
> Just curious, for your use case, wouldn't it be enough to pass N_MEMORY?
>
> Or do we expect to pass other actual node masks that are not derived from
> node_states?
>
That is the base case today - and in fact that's what's hard-coded
in the current logic.
For the private node series we will pass:
ZONELIST_FALLBACK: N_MEMORY_GENERAL /* general purpose nodes */
ZONELIST_PRIVATE: N_MEMORY /* All nodes, including private */
ZONELIST_KTEST: (N_MEMORY_GENERAL ^ N_MEMORY) /* private only */
the ZONELIST_KTEST is something i'm still working with - but it gives us
fully isolated pgdat structures that allow a KTEST to treat the node
mutations as determinstic and otherwise unreachable by the kernel
(because the kernel outside ktest can't select ZOENLIST_KTEST).
ANYWAY...
I don't expect us to build hundreds of these zonelists - they are
expensive to construct (space-wise), but that's largely because we
haven't needed to optimize it.
But you could imagine having a custom zonelist built on a subset
of N_MEMORY so that you could do like...
for_each_reclaimable_zone(z) {
/* Reclaim */
}
and hide all this nasty zone business from other components of mm/ that
really don't need all that complexity (at most they want the LRUs).
~Gregory
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-12 3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
2026-09-14 14:56 ` Vlastimil Babka (SUSE)
@ 2026-09-21 2:49 ` Zi Yan
2026-09-21 2:53 ` Zi Yan
2026-09-21 3:29 ` Gregory Price
1 sibling, 2 replies; 20+ messages in thread
From: Zi Yan @ 2026-09-21 2:49 UTC (permalink / raw)
To: Gregory Price, linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, brendan.jackman, hannes
On Fri Sep 11, 2026 at 11:04 PM EDT, Gregory Price wrote:
> Extract per-node fallback-list construction into build_node_zonelist().
> Build each selected node directly into the destination zonelist so no
> intermediate node_order array or node count is needed. Print the fallback
> order as each node is added.
>
> 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 and prints 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 | 63 ++++++++++++++++++-------------------------------
> 1 file changed, 23 insertions(+), 40 deletions(-)
>
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 7efce139d562..d1888d5630e0 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -5857,31 +5857,6 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
> }
>
>
> -/*
> - * Build zonelists ordered by node and zones within node.
> - * This results in maximum locality--normal zone overflows into local
> - * 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)
> -{
> - struct zoneref *zonerefs;
> - int i;
> -
> - zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
> -
> - for (i = 0; i < nr_nodes; i++) {
> - int nr_zones;
> -
> - pg_data_t *node = NODE_DATA(node_order[i]);
> -
> - nr_zones = build_zonerefs_node(node, zonerefs);
> - zonerefs += nr_zones;
> - }
> - zonerefs->zone = NULL;
> - zonerefs->zone_idx = 0;
> -}
> -
> /*
> * Build __GFP_THISNODE zonelists
> */
> @@ -5897,20 +5872,24 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
> zonerefs->zone_idx = 0;
> }
>
> -static void build_zonelists(pg_data_t *pgdat)
> +/*
> + * Build one zonelist ordered by node and zones within node. This results in
> + * maximum locality--normal zone overflows into local DMA zone, if any--but
> + * risks exhausting DMA zone.
> + */
> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
> + int zlidx)
> {
> - static int node_order[MAX_NUMNODES];
> - int node, nr_nodes = 0;
> + struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
Why does build_node_zonelist() need to have a new zlidx instead of using
ZONELIST_FALLBACK like build_zonelists_in_node_order() did?
> nodemask_t used_mask = NODE_MASK_NONE;
> - int local_node, prev_node;
> + int local_node = pgdat->node_id;
> + int prev_node = local_node;
> + int node;
>
> - /* NUMA-aware ordering of nodes */
> - local_node = pgdat->node_id;
> - prev_node = local_node;
> + pr_info("Fallback order for Node %d: ", local_node);
>
> - 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
> @@ -5920,18 +5899,22 @@ static void build_zonelists(pg_data_t *pgdat)
> node_distance(local_node, prev_node))
> node_load[node] += 1;
>
> - node_order[nr_nodes++] = node;
> + zonerefs += build_zonerefs_node(NODE_DATA(node), zonerefs);
> + pr_cont("%d ", node);
> prev_node = node;
> }
>
> - build_zonelists_in_node_order(pgdat, 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]);
> + zonerefs->zone = NULL;
> + zonerefs->zone_idx = 0;
> pr_cont("\n");
> }
>
> +static void build_zonelists(pg_data_t *pgdat)
> +{
> + build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
> + build_thisnode_zonelists(pgdat);
If build_thisnode_zonelists() means ZONELIST_NOFALLBACK, why
cannot build_node_zonelist() imply ZONELIST_FALLBACK?
> +}
> +
> #ifdef CONFIG_HAVE_MEMORYLESS_NODES
> /*
> * Return node id of node used for "local" allocations.
--
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 2:49 ` Zi Yan
@ 2026-09-21 2:53 ` Zi Yan
2026-09-21 3:33 ` Gregory Price
2026-09-21 3:29 ` Gregory Price
1 sibling, 1 reply; 20+ messages in thread
From: Zi Yan @ 2026-09-21 2:53 UTC (permalink / raw)
To: Gregory Price, linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, brendan.jackman, hannes
On Sun Sep 20, 2026 at 10:49 PM EDT, Zi Yan wrote:
> On Fri Sep 11, 2026 at 11:04 PM EDT, Gregory Price wrote:
>> Extract per-node fallback-list construction into build_node_zonelist().
>> Build each selected node directly into the destination zonelist so no
>> intermediate node_order array or node count is needed. Print the fallback
>> order as each node is added.
>>
>> 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 and prints 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 | 63 ++++++++++++++++++-------------------------------
>> 1 file changed, 23 insertions(+), 40 deletions(-)
>>
>> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
>> index 7efce139d562..d1888d5630e0 100644
>> --- a/mm/page_alloc.c
>> +++ b/mm/page_alloc.c
>> @@ -5857,31 +5857,6 @@ int find_next_best_node_in(int node, nodemask_t *used_node_mask,
>> }
>>
>>
>> -/*
>> - * Build zonelists ordered by node and zones within node.
>> - * This results in maximum locality--normal zone overflows into local
>> - * 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)
>> -{
>> - struct zoneref *zonerefs;
>> - int i;
>> -
>> - zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs;
>> -
>> - for (i = 0; i < nr_nodes; i++) {
>> - int nr_zones;
>> -
>> - pg_data_t *node = NODE_DATA(node_order[i]);
>> -
>> - nr_zones = build_zonerefs_node(node, zonerefs);
>> - zonerefs += nr_zones;
>> - }
>> - zonerefs->zone = NULL;
>> - zonerefs->zone_idx = 0;
>> -}
>> -
>> /*
>> * Build __GFP_THISNODE zonelists
>> */
>> @@ -5897,20 +5872,24 @@ static void build_thisnode_zonelists(pg_data_t *pgdat)
>> zonerefs->zone_idx = 0;
>> }
>>
>> -static void build_zonelists(pg_data_t *pgdat)
>> +/*
>> + * Build one zonelist ordered by node and zones within node. This results in
>> + * maximum locality--normal zone overflows into local DMA zone, if any--but
>> + * risks exhausting DMA zone.
>> + */
>> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
>> + int zlidx)
Is build_numa_aware_zonelist() a better name? Since the old comment said
"NUMA-aware ording of nodes" and the code uses find_next_best_node_in().
>> {
>> - static int node_order[MAX_NUMNODES];
>> - int node, nr_nodes = 0;
>> + struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
>
> Why does build_node_zonelist() need to have a new zlidx instead of using
> ZONELIST_FALLBACK like build_zonelists_in_node_order() did?
>
>> nodemask_t used_mask = NODE_MASK_NONE;
>> - int local_node, prev_node;
>> + int local_node = pgdat->node_id;
>> + int prev_node = local_node;
>> + int node;
>>
>> - /* NUMA-aware ordering of nodes */
>> - local_node = pgdat->node_id;
>> - prev_node = local_node;
>> + pr_info("Fallback order for Node %d: ", local_node);
>>
>> - 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
>> @@ -5920,18 +5899,22 @@ static void build_zonelists(pg_data_t *pgdat)
>> node_distance(local_node, prev_node))
>> node_load[node] += 1;
>>
>> - node_order[nr_nodes++] = node;
>> + zonerefs += build_zonerefs_node(NODE_DATA(node), zonerefs);
>> + pr_cont("%d ", node);
>> prev_node = node;
>> }
>>
>> - build_zonelists_in_node_order(pgdat, 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]);
>> + zonerefs->zone = NULL;
>> + zonerefs->zone_idx = 0;
>> pr_cont("\n");
>> }
>>
>> +static void build_zonelists(pg_data_t *pgdat)
>> +{
>> + build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
>> + build_thisnode_zonelists(pgdat);
>
> If build_thisnode_zonelists() means ZONELIST_NOFALLBACK, why
> cannot build_node_zonelist() imply ZONELIST_FALLBACK?
>
>> +}
>> +
>> #ifdef CONFIG_HAVE_MEMORYLESS_NODES
>> /*
>> * Return node id of node used for "local" allocations.
--
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 2:49 ` Zi Yan
2026-09-21 2:53 ` Zi Yan
@ 2026-09-21 3:29 ` Gregory Price
2026-09-21 16:18 ` Zi Yan
1 sibling, 1 reply; 20+ messages in thread
From: Gregory Price @ 2026-09-21 3:29 UTC (permalink / raw)
To: Zi Yan
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes
On Sun, Sep 20, 2026 at 10:49:05PM -0400, Zi Yan wrote:
> On Fri Sep 11, 2026 at 11:04 PM EDT, Gregory Price wrote:
> > +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
> > + int zlidx)
> > {
> > - static int node_order[MAX_NUMNODES];
> > - int node, nr_nodes = 0;
> > + struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
>
> Why does build_node_zonelist() need to have a new zlidx instead of using
> ZONELIST_FALLBACK like build_zonelists_in_node_order() did?
>
The intent is to build new zonelist over a set of candidate nodes, and
it's also just clearer: build ZONELIST_FALLBACK from N_MEMORY.
With this we get:
build_node_zonelists(pgdat, &node_states[N_MEMORY_COMMON], ZONELIST_FALLBACK);
build_node_zonelists(pgdat, &node_states[N_MEMORY], ZONELIST_PRIVATE);
#ifdef PAGEALLOC_KTEST
nodemask_andnot(&private_only, &node_states[N_MEMORY],
&node_states[N_MEMORY_COMMON]
build_node_zonelists(pgdat, &private_only, ZONELIST_KTEST);
#endif
> > +static void build_zonelists(pg_data_t *pgdat)
> > +{
> > + build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
> > + build_thisnode_zonelists(pgdat);
>
> If build_thisnode_zonelists() means ZONELIST_NOFALLBACK, why
> cannot build_node_zonelist() imply ZONELIST_FALLBACK?
>
thisnode actually means ZONELIST_X+1 as opposed to ZONELIST_NOFALLBACK.
Since folks are adamant about not allowing another GFP flag for zonelist
selection (beyond GFP_THISNODE), the result of this is that all future
zonelist additions must carry a FALLBACK + NOFALLBACK variant.
The question you actually want to ask is why build_thisnode_zonelists()
even exists - it should be part of build_node_zonelist()
I can probably follow up this series by just folding eveything into
/* build zlidx and zlidx+1 (nofallback) */
build_node_zonelists(pgdat, candidates, zlidx);
And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
balanced zonelist additions.
But I don't think it's strictly necessary for any of this, and we're
just shuffling code from one place to another. Probably I can just add
that improvement when we add the next zonelist. In the meantime - this
makes it easier to add new zonelists as-is (and just makes the code more
readable).
~Gregory
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 2:53 ` Zi Yan
@ 2026-09-21 3:33 ` Gregory Price
2026-09-21 16:20 ` Zi Yan
0 siblings, 1 reply; 20+ messages in thread
From: Gregory Price @ 2026-09-21 3:33 UTC (permalink / raw)
To: Zi Yan
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes
On Sun, Sep 20, 2026 at 10:53:13PM -0400, Zi Yan wrote:
> >> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
> >> + int zlidx)
>
> Is build_numa_aware_zonelist() a better name? Since the old comment said
> "NUMA-aware ording of nodes" and the code uses find_next_best_node_in().
>
I think that's a vestige of older version having different ordering
options. Now the zonelists are always numa aware (except for
!CONFIG_NUMA, which... duh).
Anyway numa-aware is implied by "node" in the name.
But I don't have strong feelings about the name either way.
~Gregory
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 3:29 ` Gregory Price
@ 2026-09-21 16:18 ` Zi Yan
2026-09-21 16:38 ` Gregory Price
0 siblings, 1 reply; 20+ messages in thread
From: Zi Yan @ 2026-09-21 16:18 UTC (permalink / raw)
To: Gregory Price
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes
On 20 Sep 2026, at 23:29, Gregory Price wrote:
> On Sun, Sep 20, 2026 at 10:49:05PM -0400, Zi Yan wrote:
>> On Fri Sep 11, 2026 at 11:04 PM EDT, Gregory Price wrote:
>>> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
>>> + int zlidx)
>>> {
>>> - static int node_order[MAX_NUMNODES];
>>> - int node, nr_nodes = 0;
>>> + struct zoneref *zonerefs = pgdat->node_zonelists[zlidx]._zonerefs;
>>
>> Why does build_node_zonelist() need to have a new zlidx instead of using
>> ZONELIST_FALLBACK like build_zonelists_in_node_order() did?
>>
>
> The intent is to build new zonelist over a set of candidate nodes, and
> it's also just clearer: build ZONELIST_FALLBACK from N_MEMORY.
>
> With this we get:
>
> build_node_zonelists(pgdat, &node_states[N_MEMORY_COMMON], ZONELIST_FALLBACK);
> build_node_zonelists(pgdat, &node_states[N_MEMORY], ZONELIST_PRIVATE);
> #ifdef PAGEALLOC_KTEST
> nodemask_andnot(&private_only, &node_states[N_MEMORY],
> &node_states[N_MEMORY_COMMON]
> build_node_zonelists(pgdat, &private_only, ZONELIST_KTEST);
> #endif
>
OK, ZONELIST_PRIVATE and ZONELIST_KTEST are not upstream yet, right?
In theory, the new zlidx can be added when you add new ZONELIST_ types.
I am OK with adding it now, but you could mention this change in
the commit message to avoid confusion. Something like,
for bulid_node_zonelist(), use ZONELIST_FALLBACK explicitly.
>
>>> +static void build_zonelists(pg_data_t *pgdat)
>>> +{
>>> + build_node_zonelist(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK);
>>> + build_thisnode_zonelists(pgdat);
>>
>> If build_thisnode_zonelists() means ZONELIST_NOFALLBACK, why
>> cannot build_node_zonelist() imply ZONELIST_FALLBACK?
>>
>
> thisnode actually means ZONELIST_X+1 as opposed to ZONELIST_NOFALLBACK.
>
> Since folks are adamant about not allowing another GFP flag for zonelist
> selection (beyond GFP_THISNODE), the result of this is that all future
> zonelist additions must carry a FALLBACK + NOFALLBACK variant.
>
> The question you actually want to ask is why build_thisnode_zonelists()
> even exists - it should be part of build_node_zonelist()
>
> I can probably follow up this series by just folding eveything into
>
> /* build zlidx and zlidx+1 (nofallback) */
> build_node_zonelists(pgdat, candidates, zlidx);
>
> And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
> balanced zonelist additions.
Got it. Thank you for the explanation. Are all combinations of
{ZONELIST_FALLBACK, ZONELIST_PRIVATE, ZONELIST_KTEST} x
{N_MEMORY_GENERAL, N_MEMORY, /* private only */} allowed?
Any enforcement if not? This is more related your “private node” series,
instead of this patchset.
>
> But I don't think it's strictly necessary for any of this, and we're
> just shuffling code from one place to another. Probably I can just add
> that improvement when we add the next zonelist. In the meantime - this
> makes it easier to add new zonelists as-is (and just makes the code more
> readable).
Sure, no rush.
Feel free to add
Reviewed-by: Zi Yan <ziy@nvidia.com>
after you add some text on the added zlidx in the commit message.
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 3:33 ` Gregory Price
@ 2026-09-21 16:20 ` Zi Yan
0 siblings, 0 replies; 20+ messages in thread
From: Zi Yan @ 2026-09-21 16:20 UTC (permalink / raw)
To: Gregory Price
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes
On 20 Sep 2026, at 23:33, Gregory Price wrote:
> On Sun, Sep 20, 2026 at 10:53:13PM -0400, Zi Yan wrote:
>>>> +static void build_node_zonelist(pg_data_t *pgdat, const nodemask_t *candidates,
>>>> + int zlidx)
>>
>> Is build_numa_aware_zonelist() a better name? Since the old comment said
>> "NUMA-aware ording of nodes" and the code uses find_next_best_node_in().
>>
>
> I think that's a vestige of older version having different ordering
> options. Now the zonelists are always numa aware (except for
> !CONFIG_NUMA, which... duh).
Yeah, I am OK with the current name to avoid confusion on !CONFIG_NUMA.
>
> Anyway numa-aware is implied by "node" in the name.
>
> But I don't have strong feelings about the name either way.
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 16:18 ` Zi Yan
@ 2026-09-21 16:38 ` Gregory Price
2026-09-21 17:04 ` Gregory Price
` (2 more replies)
0 siblings, 3 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-21 16:38 UTC (permalink / raw)
To: Zi Yan
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes
On Mon, Sep 21, 2026 at 12:18:12PM -0400, Zi Yan wrote:
> On 20 Sep 2026, at 23:29, Gregory Price wrote:
>
> OK, ZONELIST_PRIVATE and ZONELIST_KTEST are not upstream yet, right?
> In theory, the new zlidx can be added when you add new ZONELIST_ types.
>
> I am OK with adding it now, but you could mention this change in
> the commit message to avoid confusion. Something like,
> for bulid_node_zonelist(), use ZONELIST_FALLBACK explicitly.
>
...
> > And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
> > balanced zonelist additions.
>
> Got it. Thank you for the explanation. Are all combinations of
> {ZONELIST_FALLBACK, ZONELIST_PRIVATE, ZONELIST_KTEST} x
> {N_MEMORY_GENERAL, N_MEMORY, /* private only */} allowed?
Slight inaccuracy on the way zonelists are actually built.
Every node has a list in every zonelist. The contents of each node's
list in that zonelist are limited to the candidate nodemask.
So consider the following nodes: N0, N1, P2 (N=normal, P=private)
With the following candidate mappings:
FALLBACK = N_MEMORY_GENERAL (_COMMON) (all normal nodes)
PRIVATE = N_MEMORY (all nodes)
KTEST = N_MEMORY & !N_MEMORY_GENERAL (only private nodes)
FALLBACK
0 : [0,1]
1 : [1,0]
2 : [0,1] <- private node's fallback list is normal nodes
NOFALLBACK
0 : [0]
1 : [1]
2 : [2]
PRIVATE Accessible via ALLOC_PRIVATE_ZONELIST
0 : [0,1,2]
1 : [1,0,2]
2 : [2,0,1]
PRIVATE_NOFALLBACK
0 : [0]
1 : [1]
2 : [2]
KTEST Not accessible via any flag, completely isolated
0 : [2]
1 : [2]
2 : [2]
KTEST_NOFALLBACK Not accessible via any flag, completely isolated
0 : []
1 : []
2 : [2]
I haven't posted the ktest series yet, but it's how i've made the page
allocator ktest-able. Ktest is empowered to set the fallback list
directly rather than requiring a flag - not something any in-tree caller
can do - which lets it limit allocations to a private node and makes
mutations on the pgdat for that node deterministic from test-to-test.
Works on UML too, so testing is very fast. :]
Apologies for the added complexity in the explanation, but I figure it's
worth spelling out.
> Any enforcement if not? This is more related your “private node” series,
> instead of this patchset.
>
These zonelists aren't something we're going to allocate dynamically,
the enforcement is encoded in the function (build ZONELIST_X over
candidates N_MEMORY_Y).
> >
> > But I don't think it's strictly necessary for any of this, and we're
> > just shuffling code from one place to another. Probably I can just add
> > that improvement when we add the next zonelist. In the meantime - this
> > makes it easier to add new zonelists as-is (and just makes the code more
> > readable).
>
> Sure, no rush.
>
> Feel free to add
>
> Reviewed-by: Zi Yan <ziy@nvidia.com>
>
> after you add some text on the added zlidx in the commit message.
>
ack. hopefully the explanation above helps.
I will work that into the commit message in a reduced capacity.
~Gregory
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 16:38 ` Gregory Price
@ 2026-09-21 17:04 ` Gregory Price
2026-09-21 17:18 ` Zi Yan
2026-09-21 23:58 ` Zi Yan
2 siblings, 0 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-21 17:04 UTC (permalink / raw)
To: Zi Yan
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes
On Mon, Sep 21, 2026 at 12:38:57PM -0400, Gregory Price wrote:
> On Mon, Sep 21, 2026 at 12:18:12PM -0400, Zi Yan wrote:
> NOFALLBACK
> 0 : [0]
> 1 : [1]
> 2 : [2]
NOFALLBACK for a private node should be empty, sorry:
0 : [0]
1 : [1]
2 : []
if you attempt alloc(private_node, GFP_THISNODE) without selecting the
private zonelist, your allocation should fail.
But this is more discussion for the private node series.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 16:38 ` Gregory Price
2026-09-21 17:04 ` Gregory Price
@ 2026-09-21 17:18 ` Zi Yan
2026-09-21 23:58 ` Zi Yan
2 siblings, 0 replies; 20+ messages in thread
From: Zi Yan @ 2026-09-21 17:18 UTC (permalink / raw)
To: Gregory Price
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes
On 21 Sep 2026, at 12:38, Gregory Price wrote:
> On Mon, Sep 21, 2026 at 12:18:12PM -0400, Zi Yan wrote:
>> On 20 Sep 2026, at 23:29, Gregory Price wrote:
>>
>> OK, ZONELIST_PRIVATE and ZONELIST_KTEST are not upstream yet, right?
>> In theory, the new zlidx can be added when you add new ZONELIST_ types.
>>
>> I am OK with adding it now, but you could mention this change in
>> the commit message to avoid confusion. Something like,
>> for bulid_node_zonelist(), use ZONELIST_FALLBACK explicitly.
>>
> ...
>>> And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
>>> balanced zonelist additions.
>>
>> Got it. Thank you for the explanation. Are all combinations of
>> {ZONELIST_FALLBACK, ZONELIST_PRIVATE, ZONELIST_KTEST} x
>> {N_MEMORY_GENERAL, N_MEMORY, /* private only */} allowed?
>
> Slight inaccuracy on the way zonelists are actually built.
>
> Every node has a list in every zonelist. The contents of each node's
> list in that zonelist are limited to the candidate nodemask.
Got it. It makes sense to me now. Thanks.
>
> So consider the following nodes: N0, N1, P2 (N=normal, P=private)
>
> With the following candidate mappings:
> FALLBACK = N_MEMORY_GENERAL (_COMMON) (all normal nodes)
> PRIVATE = N_MEMORY (all nodes)
> KTEST = N_MEMORY & !N_MEMORY_GENERAL (only private nodes)
>
> FALLBACK
> 0 : [0,1]
> 1 : [1,0]
> 2 : [0,1] <- private node's fallback list is normal nodes
>
> NOFALLBACK
> 0 : [0]
> 1 : [1]
> 2 : [2]
>
> PRIVATE Accessible via ALLOC_PRIVATE_ZONELIST
> 0 : [0,1,2]
> 1 : [1,0,2]
> 2 : [2,0,1]
>
> PRIVATE_NOFALLBACK
> 0 : [0]
> 1 : [1]
> 2 : [2]
>
> KTEST Not accessible via any flag, completely isolated
> 0 : [2]
> 1 : [2]
> 2 : [2]
>
> KTEST_NOFALLBACK Not accessible via any flag, completely isolated
> 0 : []
> 1 : []
> 2 : [2]
>
> I haven't posted the ktest series yet, but it's how i've made the page
> allocator ktest-able. Ktest is empowered to set the fallback list
> directly rather than requiring a flag - not something any in-tree caller
> can do - which lets it limit allocations to a private node and makes
> mutations on the pgdat for that node deterministic from test-to-test.
>
> Works on UML too, so testing is very fast. :]
>
> Apologies for the added complexity in the explanation, but I figure it's
> worth spelling out.
Definitely. Great information.
>
>> Any enforcement if not? This is more related your “private node” series,
>> instead of this patchset.
>>
>
> These zonelists aren't something we're going to allocate dynamically,
> the enforcement is encoded in the function (build ZONELIST_X over
> candidates N_MEMORY_Y).
ack.
>
>>>
>>> But I don't think it's strictly necessary for any of this, and we're
>>> just shuffling code from one place to another. Probably I can just add
>>> that improvement when we add the next zonelist. In the meantime - this
>>> makes it easier to add new zonelists as-is (and just makes the code more
>>> readable).
>>
>> Sure, no rush.
>>
>> Feel free to add
>>
>> Reviewed-by: Zi Yan <ziy@nvidia.com>
>>
>> after you add some text on the added zlidx in the commit message.
>>
>
> ack. hopefully the explanation above helps.
Yep. :)
>
> I will work that into the commit message in a reduced capacity.
To be clear, I just would like you to mention the addition of
zlidx to bulid_node_zonelist(). Thanks.
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 16:38 ` Gregory Price
2026-09-21 17:04 ` Gregory Price
2026-09-21 17:18 ` Zi Yan
@ 2026-09-21 23:58 ` Zi Yan
2026-09-22 1:02 ` Gregory Price
2 siblings, 1 reply; 20+ messages in thread
From: Zi Yan @ 2026-09-21 23:58 UTC (permalink / raw)
To: Gregory Price
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes
On 21 Sep 2026, at 12:38, Gregory Price wrote:
> On Mon, Sep 21, 2026 at 12:18:12PM -0400, Zi Yan wrote:
>> On 20 Sep 2026, at 23:29, Gregory Price wrote:
>>
>> OK, ZONELIST_PRIVATE and ZONELIST_KTEST are not upstream yet, right?
>> In theory, the new zlidx can be added when you add new ZONELIST_ types.
>>
>> I am OK with adding it now, but you could mention this change in
>> the commit message to avoid confusion. Something like,
>> for bulid_node_zonelist(), use ZONELIST_FALLBACK explicitly.
>>
> ...
>>> And add a BUILD_ON_BUG/ASSERT that forces any CONFIG_NUMA to have
>>> balanced zonelist additions.
>>
>> Got it. Thank you for the explanation. Are all combinations of
>> {ZONELIST_FALLBACK, ZONELIST_PRIVATE, ZONELIST_KTEST} x
>> {N_MEMORY_GENERAL, N_MEMORY, /* private only */} allowed?
>
> Slight inaccuracy on the way zonelists are actually built.
>
> Every node has a list in every zonelist. The contents of each node's
> list in that zonelist are limited to the candidate nodemask.
>
> So consider the following nodes: N0, N1, P2 (N=normal, P=private)
>
> With the following candidate mappings:
> FALLBACK = N_MEMORY_GENERAL (_COMMON) (all normal nodes)
> PRIVATE = N_MEMORY (all nodes)
> KTEST = N_MEMORY & !N_MEMORY_GENERAL (only private nodes)
>
One more question. What prevents one adding
build_node_zonelists(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK)?
Based on your above mapping, each of ZONELIST_* has its own candidate.
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists()
2026-09-21 23:58 ` Zi Yan
@ 2026-09-22 1:02 ` Gregory Price
0 siblings, 0 replies; 20+ messages in thread
From: Gregory Price @ 2026-09-22 1:02 UTC (permalink / raw)
To: Zi Yan
Cc: linux-mm, linux-kernel, kernel-team, akpm, david, ljs, liam,
vbabka, rppt, surenb, mhocko, brendan.jackman, hannes
On Mon, Sep 21, 2026 at 07:58:38PM -0400, Zi Yan wrote:
> On 21 Sep 2026, at 12:38, Gregory Price wrote:
>
> One more question. What prevents one adding
> build_node_zonelists(pgdat, &node_states[N_MEMORY], ZONELIST_FALLBACK)?
> Based on your above mapping, each of ZONELIST_* has its own candidate.
>
Other than build_zonelist and build_node_zonelist being static functions
only reachable by page_alloc.c?
Nothing presently. It could possibly be asserted.
~Gregory
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-09-22 1:02 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-12 3:04 [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Gregory Price
2026-09-12 3:04 ` [PATCH v2 1/2] mm: refactor find_next_best_node to find_next_best_node_in Gregory Price
2026-09-14 15:00 ` Zi Yan
2026-09-18 20:42 ` David Hildenbrand (Arm)
2026-09-18 21:49 ` Gregory Price
2026-09-12 3:04 ` [PATCH v2 2/2] mm/page_alloc: refactor build_node_zonelist() out of build_zonelists() Gregory Price
2026-09-14 14:56 ` Vlastimil Babka (SUSE)
2026-09-14 15:04 ` Gregory Price
2026-09-21 2:49 ` Zi Yan
2026-09-21 2:53 ` Zi Yan
2026-09-21 3:33 ` Gregory Price
2026-09-21 16:20 ` Zi Yan
2026-09-21 3:29 ` Gregory Price
2026-09-21 16:18 ` Zi Yan
2026-09-21 16:38 ` Gregory Price
2026-09-21 17:04 ` Gregory Price
2026-09-21 17:18 ` Zi Yan
2026-09-21 23:58 ` Zi Yan
2026-09-22 1:02 ` Gregory Price
2026-09-15 16:27 ` [PATCH v2 0/2] mm: refactor zonelist constructors and iterators Zenghui Yu
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.