From: Donet Tom <hidden> Date: 2025-05-26 14:50:55
During node device initialization, `memory blocks` are registered under
each NUMA node. The `memory blocks` to be registered are identified using
the node’s start and end PFNs, which are obtained from the node's pg_data
However, not all PFNs within this range necessarily belong to the same
node—some may belong to other nodes. Additionally, due to the
discontiguous nature of physical memory, certain sections within a
`memory block` may be absent.
As a result, `memory blocks` that fall between a node’s start and end
PFNs may span across multiple nodes, and some sections within those blocks
may be missing. `Memory blocks` have a fixed size, which is architecture
dependent.
Due to these considerations, the memory block registration is currently
performed as follows:
for_each_online_node(nid):
start_pfn = pgdat->node_start_pfn;
end_pfn = pgdat->node_start_pfn + node_spanned_pages;
for_each_memory_block_between(PFN_PHYS(start_pfn), PFN_PHYS(end_pfn))
mem_blk = memory_block_id(pfn_to_section_nr(pfn));
pfn_mb_start=section_nr_to_pfn(mem_blk->start_section_nr)
pfn_mb_end = pfn_start + memory_block_pfns - 1
for (pfn = pfn_mb_start; pfn < pfn_mb_end; pfn++):
if (get_nid_for_pfn(pfn) != nid):
continue;
else
do_register_memory_block_under_node(nid, mem_blk,
MEMINIT_EARLY);
Here, we derive the start and end PFNs from the node's pg_data, then
determine the memory blocks that may belong to the node. For each
`memory block` in this range, we inspect all PFNs it contains and check
their associated NUMA node ID. If a PFN within the block matches the
current node, the memory block is registered under that node.
If CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled, get_nid_for_pfn() performs
a binary search in the `memblock regions` to determine the NUMA node ID
for a given PFN. If it is not enabled, the node ID is retrieved directly
from the struct page.
On large systems, this process can become time-consuming, especially since
we iterate over each `memory block` and all PFNs within it until a match is
found. When CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled, the additional
overhead of the binary search increases the execution time significantly,
potentially leading to soft lockups during boot.
In this patch, we iterate over `memblock region` to identify the
`memory blocks` that belong to the current NUMA node. `memblock regions`
are contiguous memory ranges, each associated with a single NUMA node, and
they do not span across multiple nodes.
for_each_memory_region(r): // r => region
if (!node_online(r->nid)):
continue;
else
for_each_memory_block_between(r->base, r->base + r->size - 1):
do_register_memory_block_under_node(r->nid, mem_blk, MEMINIT_EARLY);
We iterate over all memblock regions, and if the node associated with the
region is online, we calculate the start and end memory blocks based on the
region's start and end PFNs. We then register all the memory blocks within
that range under the region node.
Test Results on My system with 32TB RAM
=======================================
1. Boot time with CONFIG_DEFERRED_STRUCT_PAGE_INIT enabled.
Without this patch
------------------
Startup finished in 1min 16.528s (kernel)
With this patch
---------------
Startup finished in 17.236s (kernel) - 78% Improvement
2. Boot time with CONFIG_DEFERRED_STRUCT_PAGE_INIT disabled.
Without this patch
------------------
Startup finished in 28.320s (kernel)
With this patch
---------------
Startup finished in 15.621s (kernel) - 46% Improvement
Acked-by: Oscar Salvador <osalvador@suse.de>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: Zi Yan <ziy@nvidia.com>
Signed-off-by: Donet Tom <redacted>
---
v5 -> v6
1. Changed function name register_memory_blocks_under_node_early() to register_memory_blocks_under_nodes()
2. Added memblock_get_region_node() to get the region node.
v5 - https://lore.kernel.org/all/d2490e807b2c13950bc1d4199f22ec078cc4c56a.1747904868.git.donettom@linux.ibm.com/
v4 - https://lore.kernel.org/all/f94685be9cdc931a026999d236d7e92de29725c7.1747376551.git.donettom@linux.ibm.com/
v3 - https://lore.kernel.org/all/b49ed289096643ff5b5fbedcf1d1c1be42845a74.1746250339.git.donettom@linux.ibm.com/
v2 - https://lore.kernel.org/all/fbe1e0c7d91bf3fa9a64ff5d84b53ded1d0d5ac7.1745852397.git.donettom@linux.ibm.com/
v1 - https://lore.kernel.org/all/50142a29010463f436dc5c4feb540e5de3bb09df.1744175097.git.donettom@linux.ibm.com/
---
---
drivers/base/memory.c | 21 ++++-------------
drivers/base/node.c | 51 ++++++++++++++++++++++++++++++++++++++++--
include/linux/memory.h | 19 +++++++++++++++-
include/linux/node.h | 3 +++
4 files changed, 74 insertions(+), 20 deletions(-)
@@ -632,7 +619,7 @@ int __weak arch_get_memory_phys_device(unsigned long start_pfn)**Calledunderdevice_hotplug_lock.*/-staticstructmemory_block*find_memory_block_by_id(unsignedlongblock_id)+structmemory_block*find_memory_block_by_id(unsignedlongblock_id){structmemory_block*mem;
@@ -748,15 +748,6 @@ int unregister_cpu_under_node(unsigned int cpu, unsigned int nid)}#ifdef CONFIG_MEMORY_HOTPLUG-staticint__refget_nid_for_pfn(unsignedlongpfn)-{-#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT-if(system_state<SYSTEM_RUNNING)-returnearly_pfn_to_nid(pfn);-#endif-returnpfn_to_nid(pfn);-}-staticvoiddo_register_memory_block_under_node(intnid,structmemory_block*mem_blk,enummeminit_contextcontext)
@@ -783,46 +774,6 @@ static void do_register_memory_block_under_node(int nid,ret);}-/* register memory section under specified node if it spans that node */-staticintregister_mem_block_under_node_early(structmemory_block*mem_blk,-void*arg)-{-unsignedlongmemory_block_pfns=memory_block_size_bytes()/PAGE_SIZE;-unsignedlongstart_pfn=section_nr_to_pfn(mem_blk->start_section_nr);-unsignedlongend_pfn=start_pfn+memory_block_pfns-1;-intnid=*(int*)arg;-unsignedlongpfn;--for(pfn=start_pfn;pfn<=end_pfn;pfn++){-intpage_nid;--/*-*memoryblockcouldhaveseveralabsentsectionsfromstart.-*skippfnrangefromabsentsection-*/-if(!pfn_in_present_section(pfn)){-pfn=round_down(pfn+PAGES_PER_SECTION,-PAGES_PER_SECTION)-1;-continue;-}--/*-*Weneedtocheckifpagebelongstonidonlyattheboot-*casebecausenode'srangescanbeinterleaved.-*/-page_nid=get_nid_for_pfn(pfn);-if(page_nid<0)-continue;-if(page_nid!=nid)-continue;--do_register_memory_block_under_node(nid,mem_blk,MEMINIT_EARLY);-return0;-}-/* mem section does not span the specified node */-return0;-}-/**Duringhotplugweknowthatallpagesinthememoryblockbelongtothesame*node.
From: Donet Tom <hidden> Date: 2025-05-26 14:51:16
The register_one_node() function was a simple wrapper around
__register_one_node(). To simplify the code, register_one_node()
has been removed, and __register_one_node() has been renamed to
register_one_node().
Signed-off-by: Donet Tom <redacted>
---
arch/powerpc/platforms/pseries/pci_dlpar.c | 2 +-
drivers/base/node.c | 4 ++--
include/linux/node.h | 13 +------------
mm/memory_hotplug.c | 2 +-
4 files changed, 5 insertions(+), 16 deletions(-)
@@ -128,14 +128,7 @@ extern void unregister_node(struct node *node);#ifdef CONFIG_NUMAexternvoidnode_dev_init(void);/* Core of the node registration - only memory hotplug should use this */-externint__register_one_node(intnid);--/* Registers an online node */-staticinlineintregister_one_node(intnid)-{-return__register_one_node(nid);-}-+externintregister_one_node(intnid);externvoidunregister_one_node(intnid);externintregister_cpu_under_node(unsignedintcpu,unsignedintnid);externintunregister_cpu_under_node(unsignedintcpu,unsignedintnid);
@@ -148,10 +141,6 @@ extern int register_memory_node_under_compute_node(unsigned int mem_nid,staticinlinevoidnode_dev_init(void){}-staticinlineint__register_one_node(intnid)-{-return0;-}staticinlineintregister_one_node(intnid){return0;
From: David Hildenbrand <hidden> Date: 2025-05-26 17:17:19
On 26.05.25 16:50, Donet Tom wrote:
During node device initialization, `memory blocks` are registered under
each NUMA node. The `memory blocks` to be registered are identified using
the node’s start and end PFNs, which are obtained from the node's pg_data
However, not all PFNs within this range necessarily belong to the same
node—some may belong to other nodes. Additionally, due to the
discontiguous nature of physical memory, certain sections within a
`memory block` may be absent.
As a result, `memory blocks` that fall between a node’s start and end
PFNs may span across multiple nodes, and some sections within those blocks
may be missing. `Memory blocks` have a fixed size, which is architecture
dependent.
Due to these considerations, the memory block registration is currently
performed as follows:
for_each_online_node(nid):
start_pfn = pgdat->node_start_pfn;
end_pfn = pgdat->node_start_pfn + node_spanned_pages;
for_each_memory_block_between(PFN_PHYS(start_pfn), PFN_PHYS(end_pfn))
mem_blk = memory_block_id(pfn_to_section_nr(pfn));
pfn_mb_start=section_nr_to_pfn(mem_blk->start_section_nr)
pfn_mb_end = pfn_start + memory_block_pfns - 1
for (pfn = pfn_mb_start; pfn < pfn_mb_end; pfn++):
if (get_nid_for_pfn(pfn) != nid):
continue;
else
do_register_memory_block_under_node(nid, mem_blk,
MEMINIT_EARLY);
Here, we derive the start and end PFNs from the node's pg_data, then
determine the memory blocks that may belong to the node. For each
`memory block` in this range, we inspect all PFNs it contains and check
their associated NUMA node ID. If a PFN within the block matches the
current node, the memory block is registered under that node.
If CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled, get_nid_for_pfn() performs
a binary search in the `memblock regions` to determine the NUMA node ID
for a given PFN. If it is not enabled, the node ID is retrieved directly
from the struct page.
On large systems, this process can become time-consuming, especially since
we iterate over each `memory block` and all PFNs within it until a match is
found. When CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled, the additional
overhead of the binary search increases the execution time significantly,
potentially leading to soft lockups during boot.
In this patch, we iterate over `memblock region` to identify the
`memory blocks` that belong to the current NUMA node. `memblock regions`
are contiguous memory ranges, each associated with a single NUMA node, and
they do not span across multiple nodes.
for_each_memory_region(r): // r => region
if (!node_online(r->nid)):
continue;
else
for_each_memory_block_between(r->base, r->base + r->size - 1):
do_register_memory_block_under_node(r->nid, mem_blk, MEMINIT_EARLY);
We iterate over all memblock regions, and if the node associated with the
region is online, we calculate the start and end memory blocks based on the
region's start and end PFNs. We then register all the memory blocks within
that range under the region node.
Test Results on My system with 32TB RAM
=======================================
1. Boot time with CONFIG_DEFERRED_STRUCT_PAGE_INIT enabled.
Without this patch
------------------
Startup finished in 1min 16.528s (kernel)
With this patch
---------------
Startup finished in 17.236s (kernel) - 78% Improvement
2. Boot time with CONFIG_DEFERRED_STRUCT_PAGE_INIT disabled.
Without this patch
------------------
Startup finished in 28.320s (kernel)
With this patch
---------------
Startup finished in 15.621s (kernel) - 46% Improvement
Acked-by: Oscar Salvador <osalvador@suse.de>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: Zi Yan <ziy@nvidia.com>
Signed-off-by: Donet Tom <redacted>
+ * This function registers all memory blocks to their corresponding nodes
+ * based on the associated memory regions. Each memory region is tied to
+ * a specific node and does not span multiple nodes. Therefore, all memory
+ * blocks within a given region are considered to belong to that node. The
+ * function iterates through each memory region and registers the memory
+ * blocks contained within that region to the respective node. Since memory
+ * blocks can span across multiple regions (and hence multiple nodes), a
+ * single memory block may be registered under more than one node if it
+ * overlaps with regions belonging to different nodes.
a) Do we need excessive doc for that?
b) It looks partially like kerneldoc, do we want to convert it to proper
one?
/**
* register_memory_blocks_under_nodes - register all memory blocks
* under the corresponding nodes
*
...
c) Maybe add a line break .. or two to make it a bit more readable.
> + */> +static void register_memory_blocks_under_nodes(void)
+{
+ struct memblock_region *r;
+
+ for_each_mem_region(r) {
+ const unsigned long start_block_id = phys_to_block_id(r->base);
+ const unsigned long end_block_id = phys_to_block_id(r->base + r->size - 1);
+ unsigned long block_id;
+ int nid = memblock_get_region_node(r);
const int nid = memblock_get_region_node(r);
unsigned long block_id;
quoted hunk
+
+ if (!node_online(nid))
+ continue;
+
+ for (block_id = start_block_id; block_id <= end_block_id; block_id++) {
+ struct memory_block *mem;
+
+ mem = find_memory_block_by_id(block_id);
+ if (!mem)
+ continue;
+
+ do_register_memory_block_under_node(nid, mem, MEMINIT_EARLY);
+ put_device(&mem->dev);
+ }
+
+ }
+}
+
void register_memory_blocks_under_node(int nid, unsigned long start_pfn,
unsigned long end_pfn,
enum meminit_context context)
@@ -971,11 +1013,16 @@ void __init node_dev_init(void) /* * Create all node devices, which will properly link the node- * to applicable memory block devices and already created cpu devices.+ * to already created cpu devices. */ for_each_online_node(i) {- ret = register_one_node(i);+ ret = __register_one_node(i); if (ret) panic("%s() failed to add node: %d\n", __func__, ret); }++ /*+ * Link the node to memory block devices+ */
This comment is rather ... superfluous. ... and it would fit into a
single line.
From: David Hildenbrand <hidden> Date: 2025-05-26 17:17:53
On 26.05.25 16:50, Donet Tom wrote:
The register_one_node() function was a simple wrapper around
__register_one_node(). To simplify the code, register_one_node()
has been removed, and __register_one_node() has been renamed to
register_one_node().
Signed-off-by: Donet Tom <redacted>
Acked-by: David Hildenbrand <redacted>
--
Cheers,
David / dhildenb
From: Donet Tom <hidden> Date: 2025-05-26 17:42:07
On 5/26/25 10:47 PM, David Hildenbrand wrote:
On 26.05.25 16:50, Donet Tom wrote:
quoted
During node device initialization, `memory blocks` are registered under
each NUMA node. The `memory blocks` to be registered are identified
using
the node’s start and end PFNs, which are obtained from the node's
pg_data
However, not all PFNs within this range necessarily belong to the same
node—some may belong to other nodes. Additionally, due to the
discontiguous nature of physical memory, certain sections within a
`memory block` may be absent.
As a result, `memory blocks` that fall between a node’s start and end
PFNs may span across multiple nodes, and some sections within those
blocks
may be missing. `Memory blocks` have a fixed size, which is architecture
dependent.
Due to these considerations, the memory block registration is currently
performed as follows:
for_each_online_node(nid):
start_pfn = pgdat->node_start_pfn;
end_pfn = pgdat->node_start_pfn + node_spanned_pages;
for_each_memory_block_between(PFN_PHYS(start_pfn),
PFN_PHYS(end_pfn))
mem_blk = memory_block_id(pfn_to_section_nr(pfn));
pfn_mb_start=section_nr_to_pfn(mem_blk->start_section_nr)
pfn_mb_end = pfn_start + memory_block_pfns - 1
for (pfn = pfn_mb_start; pfn < pfn_mb_end; pfn++):
if (get_nid_for_pfn(pfn) != nid):
continue;
else
do_register_memory_block_under_node(nid, mem_blk,
MEMINIT_EARLY);
Here, we derive the start and end PFNs from the node's pg_data, then
determine the memory blocks that may belong to the node. For each
`memory block` in this range, we inspect all PFNs it contains and check
their associated NUMA node ID. If a PFN within the block matches the
current node, the memory block is registered under that node.
If CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled, get_nid_for_pfn()
performs
a binary search in the `memblock regions` to determine the NUMA node ID
for a given PFN. If it is not enabled, the node ID is retrieved directly
from the struct page.
On large systems, this process can become time-consuming, especially
since
we iterate over each `memory block` and all PFNs within it until a
match is
found. When CONFIG_DEFERRED_STRUCT_PAGE_INIT is enabled, the additional
overhead of the binary search increases the execution time
significantly,
potentially leading to soft lockups during boot.
In this patch, we iterate over `memblock region` to identify the
`memory blocks` that belong to the current NUMA node. `memblock regions`
are contiguous memory ranges, each associated with a single NUMA
node, and
they do not span across multiple nodes.
for_each_memory_region(r): // r => region
if (!node_online(r->nid)):
continue;
else
for_each_memory_block_between(r->base, r->base + r->size - 1):
do_register_memory_block_under_node(r->nid, mem_blk,
MEMINIT_EARLY);
We iterate over all memblock regions, and if the node associated with
the
region is online, we calculate the start and end memory blocks based
on the
region's start and end PFNs. We then register all the memory blocks
within
that range under the region node.
Test Results on My system with 32TB RAM
=======================================
1. Boot time with CONFIG_DEFERRED_STRUCT_PAGE_INIT enabled.
Without this patch
------------------
Startup finished in 1min 16.528s (kernel)
With this patch
---------------
Startup finished in 17.236s (kernel) - 78% Improvement
2. Boot time with CONFIG_DEFERRED_STRUCT_PAGE_INIT disabled.
Without this patch
------------------
Startup finished in 28.320s (kernel)
With this patch
---------------
Startup finished in 15.621s (kernel) - 46% Improvement
Acked-by: Oscar Salvador <osalvador@suse.de>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: Zi Yan <ziy@nvidia.com>
Signed-off-by: Donet Tom <redacted>
memory_block *mem_blk)
kobject_name(&node_devices[mem_blk->nid]->dev.kobj));
}
+/*
+ * register_memory_blocks_under_nodes : Register the memory blocks
+ * under the nodes.> + *
+ * This function registers all memory blocks to their corresponding
nodes
+ * based on the associated memory regions. Each memory region is
tied to
+ * a specific node and does not span multiple nodes. Therefore, all
memory
+ * blocks within a given region are considered to belong to that
node. The
+ * function iterates through each memory region and registers the
memory
+ * blocks contained within that region to the respective node. Since
memory
+ * blocks can span across multiple regions (and hence multiple
nodes), a
+ * single memory block may be registered under more than one node if it
+ * overlaps with regions belonging to different nodes.
a) Do we need excessive doc for that?
b) It looks partially like kerneldoc, do we want to convert it to
proper one?
/**
* register_memory_blocks_under_nodes - register all memory blocks
* under the corresponding nodes
*
...
c) Maybe add a line break .. or two to make it a bit more readable.
Sure David, I will change it to a proper comment.
quoted
+ */> +static void register_memory_blocks_under_nodes(void)
+{
+ struct memblock_region *r;
+
+ for_each_mem_region(r) {
+ const unsigned long start_block_id = phys_to_block_id(r->base);
+ const unsigned long end_block_id = phys_to_block_id(r->base
+ r->size - 1);
+ unsigned long block_id;
+ int nid = memblock_get_region_node(r);
const int nid = memblock_get_region_node(r);
unsigned long block_id;
Sure. I will change it.
quoted
+
+ if (!node_online(nid))
+ continue;
+
+ for (block_id = start_block_id; block_id <= end_block_id;
block_id++) {
+ struct memory_block *mem;
+
+ mem = find_memory_block_by_id(block_id);
+ if (!mem)
+ continue;
+
+ do_register_memory_block_under_node(nid, mem,
MEMINIT_EARLY);
+ put_device(&mem->dev);
+ }
+
+ }
+}
+
void register_memory_blocks_under_node(int nid, unsigned long
start_pfn,
unsigned long end_pfn,
enum meminit_context context)
/*
* Create all node devices, which will properly link the node
- * to applicable memory block devices and already created cpu
devices.
+ * to already created cpu devices.
*/
for_each_online_node(i) {
- ret = register_one_node(i);
+ ret = __register_one_node(i);
if (ret)
panic("%s() failed to add node: %d\n", __func__, ret);
}
+
+ /*
+ * Link the node to memory block devices
+ */
This comment is rather ... superfluous. ... and it would fit into a
single line.