* [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
* 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 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
* [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 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 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: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: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 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 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 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
* 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
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.