* [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 [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 [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 a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®