Generic Linux architectural discussions
 help / color / mirror / Atom feed
* [PATCH v7 0/9] mm: optimize zone-device memmap initialization
@ 2026-07-20 12:02 Li Zhe
  2026-07-20 12:02 ` Li Zhe
                   ` (11 more replies)
  0 siblings, 12 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

memmap_init_zone_device() can take a noticeable amount of time when large
pmem namespaces are bound or rebound, because it initializes nearly
identical struct page descriptors one PFN at a time. This series reduces
that ZONE_DEVICE memmap initialization overhead by reusing prepared
struct page templates and, on x86, using memcpy_nontemporal() for the
template copy path.

The main target is large fsdax/devdax pmem configurations, where the
cost of initializing the memmap shows up directly in nd_pmem/dax_pmem
bind and rebind latency.

Patches 1-3 are preparatory cleanups and helper extraction. Patches 4-5
add the template-copy path for head pages and compound tails. Patches
6-8 introduce memcpy_nontemporal(), extend the x86 fixed-size
memcpy_flushcache() inline cases used by that helper, and switch the
template-copy path over to memcpy_nontemporal(). Patch 9 removes the
remaining local opt-out predicate and always uses the template path after
the first real page seeds the reusable template.

Architectures without a specialized memcpy_nontemporal() backend fall
back to memcpy(), so the template-copy optimization remains available
without arch-specific support. On x86, memcpy_nontemporal() maps to the
existing memcpy_flushcache() backend and can use the fixed-size MOVNTI
paths added by this series for struct page sized copies.

This version also incorporates a round of review comments from Sashiko
on patches 6-8.

For patch 6, the generic memcpy_nontemporal() fallback is a macro alias
to memcpy(), rather than an inline wrapper, so architectures without a
specialized backend keep the usual memcpy() FORTIFY coverage when object
sizes remain visible at the original call site.

For patch 7, I re-checked whether the new 32/48/64/80/96-byte MOVNTI
cases should add a separate alignment check before staying on the inline
fast path. I re-checked the Intel SDM together with the existing
memcpy_flushcache() behavior, and I did not find an architectural
statement that would make those new cases require a different
correctness rule from the long-standing 4/8/16-byte fast paths. This
series therefore keeps the new fixed-size cases inline without adding a
separate alignment check.

For patch 8, I re-checked whether the template-copy path needs a
helper-level drain after the MOVNTI-based copies. This series keeps
memcpy_nontemporal() as a copy primitive only. Callers that use it for a
producer-consumer or device-visible handoff must provide the required
ordering. The ZONE_DEVICE template-copy path uses it only while
initializing struct page metadata, so the copy primitive itself does not
grow a separate drain contract.

The numbers below measure the time spent in memmap_init_zone_device()
during driver bind/rebind. They are not measurements of the full
nd_pmem or dax_pmem bind/rebind operation.

Tested in a VM with a 100 GB fsdax namespace device configured with
map=dev and a 100 GB devdax namespace (align=2097152) on Intel Ice Lake
server.

Test procedure:
Rebind the nd_pmem and dax_pmem driver 30 times and collect the memmap
initialization time from the pr_debug() output of
memmap_init_zone_device().

Base(v7.2-rc1):
  First binding for nd_pmem driver: 1456 ms
  Average of subsequent rebinds: 244.28 ms

  First binding for dax_pmem driver: 1462 ms
  Average of subsequent rebinds: 273.31 ms

With this series applied:
  First binding for nd_pmem driver: 1272 ms
  Average of subsequent rebinds: 96.79 ms

  First binding for dax_pmem driver: 1354 ms
  Average of subsequent rebinds: 119.04 ms

This reduces the average memmap initialization time measured during
rebind by about 60.4% for nd_pmem and 56.4% for dax_pmem.

As an additional data point, I also ran a smaller set of measurements on
the same physical x86_64 host with a 100 GB PMEM region created via the
memmap= kernel command line, configured as fsdax and devdax namespaces
with map=dev and 2 MiB alignment.

For brevity, the individual patches keep only the VM results rather than
including a second set of physical-host measurements throughout the
series. The physical-host numbers below are included only as
supplemental evidence that the same optimization also provides a similar
benefit on a non-virtualized system.

Test procedure:
Reconfigure the namespace mode, rebind the nd_pmem or dax_pmem driver
once, and collect the memmap initialization time from the pr_debug()
output of memmap_init_zone_device().

Base (v7.2-rc1):
  nd_pmem / fsdax: 179 ms
  dax_pmem / devdax: 264 ms

With this series applied:
  nd_pmem / fsdax: 82 ms
  dax_pmem / devdax: 113 ms

This reduces the measured memmap initialization time during rebind by
about 54.2% for nd_pmem and 57.2% for dax_pmem on that setup, which is
broadly consistent with the VM results above.

As another supplemental data point, I also measured the test_hmm.ko
module on the same physical x86_64 host, using the test_hmm.ko setup
from the previous discussion that times ten 64 GB
memremap_pages()/memunmap_pages() iterations during module insertion[1].
By default, module insertion initializes two DEVICE_PRIVATE dmirror
devices, so two avg memremap values are reported; each value is the
average for one 64 GB chunk.

This is not the primary target workload of the series, but it exercises
the same large ZONE_DEVICE memmap initialization path and shows the same
direction of improvement.

Base (v7.2-rc1):
  avg memremap reported during module insertion: 116689362 ns, 116539263 ns

With this series applied:
  avg memremap reported during module insertion: 54607108 ns, 54458236 ns

This corresponds to about a 53.2% reduction based on the mean of the
reported values, which is again consistent with the pmem bind/rebind
results above.

[1] https://lore.kernel.org/all/aiEoByaQdRR3xtM5@nvdebian.thelocal/

Li Zhe (9):
  mm: fix stale ZONE_DEVICE refcount comment
  mm: factor zone-device page init helpers out of
    __init_zone_device_page
  mm: add a set_page_section_from_pfn() helper
  mm: add a template-based fast path for zone-device page init
  mm: extend the template fast path to zone-device compound tails
  string: introduce memcpy_nontemporal()
  x86/string: extend memcpy_flushcache() fixed-size fastpaths
  mm: use memcpy_nontemporal() in zone-device template copies
  mm: always use the zone-device template init path

 arch/x86/include/asm/string_64.h |  68 +++++++++++++++-
 include/linux/mm.h               |  15 +++-
 include/linux/string.h           |  12 +++
 mm/mm_init.c                     | 131 +++++++++++++++++++++++++------
 4 files changed, 198 insertions(+), 28 deletions(-)

---
v6: https://lore.kernel.org/all/20260709112520.24857-1-lizhe.67@bytedance.com/
v5: https://lore.kernel.org/all/20260701090553.62691-1-lizhe.67@bytedance.com/
v4: https://lore.kernel.org/all/20260603080152.64728-1-lizhe.67@bytedance.com/
v3: https://lore.kernel.org/all/20260527033636.28231-1-lizhe.67@bytedance.com/
v2: https://lore.kernel.org/all/20260521040124.10608-1-lizhe.67@bytedance.com/
v1: https://lore.kernel.org/all/20260515082045.63029-1-lizhe.67@bytedance.com/

Changelogs:

v6->v7:
- Rename memcpy_nt() to memcpy_nontemporal() so the generic helper name
  is explicit rather than abbreviated. Suggested by Muchun Song.
- Drop the separate memcpy_nt_drain() helper and remove the drain calls
  from the ZONE_DEVICE template-copy path. Suggested by Muchun Song.
- Rework patch 6 so the generic memcpy_nontemporal() fallback uses
  '#define memcpy_nontemporal memcpy', preserving the usual memcpy()
  FORTIFY coverage when object sizes remain visible at the original call
  site.
- Summarize the off-list Sashiko review comments relayed by Andrew on
  the memcpy helper and x86 memcpy_flushcache() pieces.
- Add patch 9 to remove the local template-enable predicate and the
  remaining non-template fallback path, as suggested by Muchun. This
  also means KASAN/KMSAN builds no longer force the per-page
  initialization path, and page_ref_set no longer observes every
  initialization-time refcount assignment.
- Re-ran the VM performance test for v7. The results were close to the
  previously reported v5 numbers, so keep the existing performance data
  unchanged.
- Rename pagemap_resets_refcount() to pagemap_requires_refcount_reset()
  and invert the predicate accordingly. Suggested by Balbir Singh.

For changelogs of earlier revisions, please refer to the v6 cover letter.

-- 
2.20.1

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

* [PATCH v7 0/9] mm: optimize zone-device memmap initialization
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 1/9] mm: fix stale ZONE_DEVICE refcount comment Li Zhe
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

memmap_init_zone_device() can take a noticeable amount of time when large
pmem namespaces are bound or rebound, because it initializes nearly
identical struct page descriptors one PFN at a time. This series reduces
that ZONE_DEVICE memmap initialization overhead by reusing prepared
struct page templates and, on x86, using memcpy_nontemporal() for the
template copy path.

The main target is large fsdax/devdax pmem configurations, where the
cost of initializing the memmap shows up directly in nd_pmem/dax_pmem
bind and rebind latency.

Patches 1-3 are preparatory cleanups and helper extraction. Patches 4-5
add the template-copy path for head pages and compound tails. Patches
6-8 introduce memcpy_nontemporal(), extend the x86 fixed-size
memcpy_flushcache() inline cases used by that helper, and switch the
template-copy path over to memcpy_nontemporal(). Patch 9 removes the
remaining local opt-out predicate and always uses the template path after
the first real page seeds the reusable template.

Architectures without a specialized memcpy_nontemporal() backend fall
back to memcpy(), so the template-copy optimization remains available
without arch-specific support. On x86, memcpy_nontemporal() maps to the
existing memcpy_flushcache() backend and can use the fixed-size MOVNTI
paths added by this series for struct page sized copies.

This version also incorporates a round of review comments from Sashiko
on patches 6-8.

For patch 6, the generic memcpy_nontemporal() fallback is a macro alias
to memcpy(), rather than an inline wrapper, so architectures without a
specialized backend keep the usual memcpy() FORTIFY coverage when object
sizes remain visible at the original call site.

For patch 7, I re-checked whether the new 32/48/64/80/96-byte MOVNTI
cases should add a separate alignment check before staying on the inline
fast path. I re-checked the Intel SDM together with the existing
memcpy_flushcache() behavior, and I did not find an architectural
statement that would make those new cases require a different
correctness rule from the long-standing 4/8/16-byte fast paths. This
series therefore keeps the new fixed-size cases inline without adding a
separate alignment check.

For patch 8, I re-checked whether the template-copy path needs a
helper-level drain after the MOVNTI-based copies. This series keeps
memcpy_nontemporal() as a copy primitive only. Callers that use it for a
producer-consumer or device-visible handoff must provide the required
ordering. The ZONE_DEVICE template-copy path uses it only while
initializing struct page metadata, so the copy primitive itself does not
grow a separate drain contract.

The numbers below measure the time spent in memmap_init_zone_device()
during driver bind/rebind. They are not measurements of the full
nd_pmem or dax_pmem bind/rebind operation.

Tested in a VM with a 100 GB fsdax namespace device configured with
map=dev and a 100 GB devdax namespace (align=2097152) on Intel Ice Lake
server.

Test procedure:
Rebind the nd_pmem and dax_pmem driver 30 times and collect the memmap
initialization time from the pr_debug() output of
memmap_init_zone_device().

Base(v7.2-rc1):
  First binding for nd_pmem driver: 1456 ms
  Average of subsequent rebinds: 244.28 ms

  First binding for dax_pmem driver: 1462 ms
  Average of subsequent rebinds: 273.31 ms

With this series applied:
  First binding for nd_pmem driver: 1272 ms
  Average of subsequent rebinds: 96.79 ms

  First binding for dax_pmem driver: 1354 ms
  Average of subsequent rebinds: 119.04 ms

This reduces the average memmap initialization time measured during
rebind by about 60.4% for nd_pmem and 56.4% for dax_pmem.

As an additional data point, I also ran a smaller set of measurements on
the same physical x86_64 host with a 100 GB PMEM region created via the
memmap= kernel command line, configured as fsdax and devdax namespaces
with map=dev and 2 MiB alignment.

For brevity, the individual patches keep only the VM results rather than
including a second set of physical-host measurements throughout the
series. The physical-host numbers below are included only as
supplemental evidence that the same optimization also provides a similar
benefit on a non-virtualized system.

Test procedure:
Reconfigure the namespace mode, rebind the nd_pmem or dax_pmem driver
once, and collect the memmap initialization time from the pr_debug()
output of memmap_init_zone_device().

Base (v7.2-rc1):
  nd_pmem / fsdax: 179 ms
  dax_pmem / devdax: 264 ms

With this series applied:
  nd_pmem / fsdax: 82 ms
  dax_pmem / devdax: 113 ms

This reduces the measured memmap initialization time during rebind by
about 54.2% for nd_pmem and 57.2% for dax_pmem on that setup, which is
broadly consistent with the VM results above.

As another supplemental data point, I also measured the test_hmm.ko
module on the same physical x86_64 host, using the test_hmm.ko setup
from the previous discussion that times ten 64 GB
memremap_pages()/memunmap_pages() iterations during module insertion[1].
By default, module insertion initializes two DEVICE_PRIVATE dmirror
devices, so two avg memremap values are reported; each value is the
average for one 64 GB chunk.

This is not the primary target workload of the series, but it exercises
the same large ZONE_DEVICE memmap initialization path and shows the same
direction of improvement.

Base (v7.2-rc1):
  avg memremap reported during module insertion: 116689362 ns, 116539263 ns

With this series applied:
  avg memremap reported during module insertion: 54607108 ns, 54458236 ns

This corresponds to about a 53.2% reduction based on the mean of the
reported values, which is again consistent with the pmem bind/rebind
results above.

[1] https://lore.kernel.org/all/aiEoByaQdRR3xtM5@nvdebian.thelocal/

Li Zhe (9):
  mm: fix stale ZONE_DEVICE refcount comment
  mm: factor zone-device page init helpers out of
    __init_zone_device_page
  mm: add a set_page_section_from_pfn() helper
  mm: add a template-based fast path for zone-device page init
  mm: extend the template fast path to zone-device compound tails
  string: introduce memcpy_nontemporal()
  x86/string: extend memcpy_flushcache() fixed-size fastpaths
  mm: use memcpy_nontemporal() in zone-device template copies
  mm: always use the zone-device template init path

 arch/x86/include/asm/string_64.h |  68 +++++++++++++++-
 include/linux/mm.h               |  15 +++-
 include/linux/string.h           |  12 +++
 mm/mm_init.c                     | 131 +++++++++++++++++++++++++------
 4 files changed, 198 insertions(+), 28 deletions(-)

---
v6: https://lore.kernel.org/all/20260709112520.24857-1-lizhe.67@bytedance.com/
v5: https://lore.kernel.org/all/20260701090553.62691-1-lizhe.67@bytedance.com/
v4: https://lore.kernel.org/all/20260603080152.64728-1-lizhe.67@bytedance.com/
v3: https://lore.kernel.org/all/20260527033636.28231-1-lizhe.67@bytedance.com/
v2: https://lore.kernel.org/all/20260521040124.10608-1-lizhe.67@bytedance.com/
v1: https://lore.kernel.org/all/20260515082045.63029-1-lizhe.67@bytedance.com/

Changelogs:

v6->v7:
- Rename memcpy_nt() to memcpy_nontemporal() so the generic helper name
  is explicit rather than abbreviated. Suggested by Muchun Song.
- Drop the separate memcpy_nt_drain() helper and remove the drain calls
  from the ZONE_DEVICE template-copy path. Suggested by Muchun Song.
- Rework patch 6 so the generic memcpy_nontemporal() fallback uses
  '#define memcpy_nontemporal memcpy', preserving the usual memcpy()
  FORTIFY coverage when object sizes remain visible at the original call
  site.
- Summarize the off-list Sashiko review comments relayed by Andrew on
  the memcpy helper and x86 memcpy_flushcache() pieces.
- Add patch 9 to remove the local template-enable predicate and the
  remaining non-template fallback path, as suggested by Muchun. This
  also means KASAN/KMSAN builds no longer force the per-page
  initialization path, and page_ref_set no longer observes every
  initialization-time refcount assignment.
- Re-ran the VM performance test for v7. The results were close to the
  previously reported v5 numbers, so keep the existing performance data
  unchanged.
- Rename pagemap_resets_refcount() to pagemap_requires_refcount_reset()
  and invert the predicate accordingly. Suggested by Balbir Singh.

For changelogs of earlier revisions, please refer to the v6 cover letter.

-- 
2.20.1

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

* [PATCH v7 1/9] mm: fix stale ZONE_DEVICE refcount comment
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
  2026-07-20 12:02 ` Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 2/9] mm: factor zone-device page init helpers out of __init_zone_device_page Li Zhe
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

The comment in __init_zone_device_page() still uses the old
MEMORY_TYPE_* names and implies that FS_DAX pages regain a
refcount of 1 in the free path. That no longer matches the code.

Update the comment to describe the current policy correctly:
MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free path,
while the remaining ZONE_DEVICE types start from 0 here and raise the
count again when the allocator or driver hands the page out.

No functional change intended.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Alistair Popple <apopple@nvidia.com>
Reviewed-by: Muchun Song <muchun.song@linux.dev>
---
 mm/mm_init.c | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/mm/mm_init.c b/mm/mm_init.c
index 0f64909e8d20..95808ab5cfdb 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1030,13 +1030,9 @@ static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
 	page->zone_device_data = NULL;
 
 	/*
-	 * ZONE_DEVICE pages other than MEMORY_TYPE_GENERIC are released
-	 * directly to the driver page allocator which will set the page count
-	 * to 1 when allocating the page.
-	 *
-	 * MEMORY_TYPE_GENERIC and MEMORY_TYPE_FS_DAX pages automatically have
-	 * their refcount reset to one whenever they are freed (ie. after
-	 * their refcount drops to 0).
+	 * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
+	 * path. The remaining ZONE_DEVICE types start from 0 here and raise
+	 * the count again when the allocator or driver hands the page out.
 	 */
 	switch (pgmap->type) {
 	case MEMORY_DEVICE_FS_DAX:
-- 
2.20.1

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

* [PATCH v7 2/9] mm: factor zone-device page init helpers out of __init_zone_device_page
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
  2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 1/9] mm: fix stale ZONE_DEVICE refcount comment Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 3/9] mm: add a set_page_section_from_pfn() helper Li Zhe
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

memmap_init_zone_device() currently mixes refcount policy and core
ZONE_DEVICE page setup in a single helper.

Factor the refcount-reset predicate into pagemap_requires_refcount_reset(),
move the common page initialization into __zone_device_page_init(), and
wrap the existing slow path in zone_device_page_init_slow().

This keeps the slow-path behaviour unchanged and gives later patches
reusable helper boundaries.

No functional change intended.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
 mm/mm_init.c | 56 ++++++++++++++++++++++++++++++++++------------------
 1 file changed, 37 insertions(+), 19 deletions(-)

diff --git a/mm/mm_init.c b/mm/mm_init.c
index 95808ab5cfdb..a70acb7431a6 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1005,11 +1005,37 @@ static void __init memmap_init(void)
 }
 
 #ifdef CONFIG_ZONE_DEVICE
-static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
+/*
+ * Return true when memmap_init_zone_device() must initialize the page
+ * refcount to 0. MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in
+ * the free path, while the remaining ZONE_DEVICE types start from 0 here
+ * and raise the count again when the allocator or driver hands the page
+ * out.
+ */
+static inline bool pagemap_requires_refcount_reset(const struct dev_pagemap *pgmap)
+{
+	/*
+	 * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
+	 * path. The remaining ZONE_DEVICE types start from 0 here and raise
+	 * the count again when the allocator or driver hands the page out.
+	 */
+	switch (pgmap->type) {
+	case MEMORY_DEVICE_FS_DAX:
+	case MEMORY_DEVICE_PRIVATE:
+	case MEMORY_DEVICE_COHERENT:
+	case MEMORY_DEVICE_PCI_P2PDMA:
+		return true;
+	case MEMORY_DEVICE_GENERIC:
+		return false;
+	}
+
+	return false;
+}
+
+static void __ref __zone_device_page_init(struct page *page, unsigned long pfn,
 					  unsigned long zone_idx, int nid,
 					  struct dev_pagemap *pgmap)
 {
-
 	__init_single_page(page, pfn, zone_idx, nid);
 
 	/*
@@ -1028,23 +1054,15 @@ static void __ref __init_zone_device_page(struct page *page, unsigned long pfn,
 	 */
 	page_folio(page)->pgmap = pgmap;
 	page->zone_device_data = NULL;
+}
 
-	/*
-	 * MEMORY_DEVICE_GENERIC pages regain a refcount of 1 in the free
-	 * path. The remaining ZONE_DEVICE types start from 0 here and raise
-	 * the count again when the allocator or driver hands the page out.
-	 */
-	switch (pgmap->type) {
-	case MEMORY_DEVICE_FS_DAX:
-	case MEMORY_DEVICE_PRIVATE:
-	case MEMORY_DEVICE_COHERENT:
-	case MEMORY_DEVICE_PCI_P2PDMA:
+static void __ref zone_device_page_init_slow(struct page *page,
+		unsigned long pfn, unsigned long zone_idx, int nid,
+		struct dev_pagemap *pgmap)
+{
+	__zone_device_page_init(page, pfn, zone_idx, nid, pgmap);
+	if (pagemap_requires_refcount_reset(pgmap))
 		set_page_count(page, 0);
-		break;
-
-	case MEMORY_DEVICE_GENERIC:
-		break;
-	}
 }
 
 /*
@@ -1090,7 +1108,7 @@ static void __ref memmap_init_compound(struct page *head,
 	for (pfn = head_pfn + 1; pfn < end_pfn; pfn++) {
 		struct page *page = pfn_to_page(pfn);
 
-		__init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
+		zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
 		prep_compound_tail(page, head, order);
 		set_page_count(page, 0);
 	}
@@ -1126,7 +1144,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
 	for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
 		struct page *page = pfn_to_page(pfn);
 
-		__init_zone_device_page(page, pfn, zone_idx, nid, pgmap);
+		zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
 
 		if (IS_ALIGNED(pfn, PAGES_PER_SECTION))
 			cond_resched();
-- 
2.20.1

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

* [PATCH v7 3/9] mm: add a set_page_section_from_pfn() helper
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
                   ` (2 preceding siblings ...)
  2026-07-20 12:02 ` [PATCH v7 2/9] mm: factor zone-device page init helpers out of __init_zone_device_page Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 4/9] mm: add a template-based fast path for zone-device page init Li Zhe
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

Callers that want to update section bits from a PFN currently need to
open-code:

	set_page_section(page, pfn_to_section_nr(pfn));

and guard that sequence with #ifdef SECTION_IN_PAGE_FLAGS.

Add set_page_section_from_pfn() to wrap that update in one place. When
section bits are stored in page flags, the helper derives the section
number from the PFN and updates the page flags. Otherwise keep it as a
no-op so callers can use one helper without open-coding
SECTION_IN_PAGE_FLAGS.

Convert set_page_links() to use the new helper so later ZONE_DEVICE
fast-path patches can also update section bits without open-coding
SECTION_IN_PAGE_FLAGS at each callsite.

This keeps the PFN-to-section translation local to the configurations
that actually store section bits in struct page flags, and avoids
exposing that detail to generic callers.

No functional change intended.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: Muchun Song <muchun.song@linux.dev>
Reviewed-by: Balbir Singh <balbirs@nvidia.com>
---
 include/linux/mm.h | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 485df9c2dbdd..43343bfce493 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2541,11 +2541,22 @@ static inline void set_page_section(struct page *page, unsigned long section)
 	page->flags.f |= (section & SECTIONS_MASK) << SECTIONS_PGSHIFT;
 }
 
+static inline void set_page_section_from_pfn(struct page *page,
+		unsigned long pfn)
+{
+	set_page_section(page, pfn_to_section_nr(pfn));
+}
+
 static inline unsigned long memdesc_section(memdesc_flags_t mdf)
 {
 	return (mdf.f >> SECTIONS_PGSHIFT) & SECTIONS_MASK;
 }
 #else /* !SECTION_IN_PAGE_FLAGS */
+static inline void set_page_section_from_pfn(struct page *page,
+		unsigned long pfn)
+{
+}
+
 static inline unsigned long memdesc_section(memdesc_flags_t mdf)
 {
 	return 0;
@@ -2768,9 +2779,7 @@ static inline void set_page_links(struct page *page, enum zone_type zone,
 {
 	set_page_zone(page, zone);
 	set_page_node(page, node);
-#ifdef SECTION_IN_PAGE_FLAGS
-	set_page_section(page, pfn_to_section_nr(pfn));
-#endif
+	set_page_section_from_pfn(page, pfn);
 }
 
 /**
-- 
2.20.1

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

* [PATCH v7 4/9] mm: add a template-based fast path for zone-device page init
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
                   ` (3 preceding siblings ...)
  2026-07-20 12:02 ` [PATCH v7 3/9] mm: add a set_page_section_from_pfn() helper Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 5/9] mm: extend the template fast path to zone-device compound tails Li Zhe
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

memmap_init_zone_device() repeats nearly identical head-page
initialization for each PFN. Prepare one reusable ZONE_DEVICE head-page
template through the existing slow path, refresh the PFN-dependent
fields in that template before each copy, and memcpy it into each
destination page.

The optimized path assigns _refcount through the copied template, so
keep it disabled when the page_ref_set tracepoint is enabled.

This patch accelerates head-page initialization. The pfns_per_compound
== 1 case gets the full benefit here, compound tails are handled in the
next patch.

Tested in a VM with a 100 GB fsdax namespace device configured with
map=dev on Intel Ice Lake server. This test exercises the nd_pmem rebind
path (pfns_per_compound == 1).

Test procedure:
Rebind the nd_pmem driver 30 times and collect the memmap initialization
time from the pr_debug() output of memmap_init_zone_device().

Base(v7.2-rc1):
  First binding: 1456 ms
  Average of subsequent rebinds: 244.28 ms

With this patch and its prerequisites applied:
  First binding: 1440 ms
  Average of subsequent rebinds: 217.19 ms

This reduces the average memmap initialization time measured during rebind
from 244.28 ms to 217.19 ms, or about 11%.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 mm/mm_init.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/mm/mm_init.c b/mm/mm_init.c
index a70acb7431a6..c2645b9bcef3 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1065,6 +1065,44 @@ static void __ref zone_device_page_init_slow(struct page *page,
 		set_page_count(page, 0);
 }
 
+static inline bool zone_device_page_init_optimization_enabled(void)
+{
+	/*
+	 * The template fast path copies a preinitialized struct page image.
+	 * Skip it when the page_ref_set tracepoint is enabled.
+	 */
+	return !page_ref_tracepoint_active(page_ref_set);
+}
+
+/*
+ * 'template' is a reusable page prototype rather than a strictly immutable
+ * object. Most ZONE_DEVICE fields stay constant across the pages covered by
+ * the current template, but section bits and page->virtual may still depend
+ * on the PFN. Refresh those PFN-dependent fields in the template before
+ * copying it into @page.
+ */
+static inline void zone_device_page_update_template(struct page *template,
+		unsigned long pfn)
+{
+	set_page_section_from_pfn(template, pfn);
+#ifdef WANT_PAGE_VIRTUAL
+	if (!is_highmem_idx(ZONE_DEVICE))
+		set_page_address(template, __va(pfn << PAGE_SHIFT));
+#endif
+}
+
+static void zone_device_page_init_from_template(struct page *page,
+		unsigned long pfn, struct page *template)
+{
+	/*
+	 * 'template' carries the invariant portion of a ZONE_DEVICE struct
+	 * page. Update the PFN-dependent fields in place before copying it
+	 * to the destination page.
+	 */
+	zone_device_page_update_template(template, pfn);
+	memcpy(page, template, sizeof(*page));
+}
+
 /*
  * With compound page geometry and when struct pages are stored in ram most
  * tail pages are reused. Consequently, the amount of unique struct pages to
@@ -1120,6 +1158,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
 				   unsigned long nr_pages,
 				   struct dev_pagemap *pgmap)
 {
+	bool use_template = zone_device_page_init_optimization_enabled();
 	unsigned long pfn, end_pfn = start_pfn + nr_pages;
 	struct pglist_data *pgdat = zone->zone_pgdat;
 	struct vmem_altmap *altmap = pgmap_altmap(pgmap);
@@ -1127,6 +1166,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
 	unsigned long zone_idx = zone_idx(zone);
 	unsigned long start = jiffies;
 	int nid = pgdat->node_id;
+	struct page template;
 
 	if (WARN_ON_ONCE(!pgmap || zone_idx != ZONE_DEVICE))
 		return;
@@ -1144,7 +1184,24 @@ void __ref memmap_init_zone_device(struct zone *zone,
 	for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
 		struct page *page = pfn_to_page(pfn);
 
-		zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
+		if (!use_template) {
+			zone_device_page_init_slow(page, pfn, zone_idx,
+						   nid, pgmap);
+		} else if (pfn == start_pfn) {
+			/*
+			 * Seed the reusable head-page template from the
+			 * first real struct page, because the existing
+			 * page-init and pageblock helpers expect a real
+			 * memmap entry rather than a stack object.
+			 */
+			zone_device_page_init_slow(page, pfn, zone_idx,
+						   nid, pgmap);
+			/* init template page */
+			memcpy(&template, page, sizeof(*page));
+		} else {
+			zone_device_page_init_from_template(page, pfn,
+							    &template);
+		}
 
 		if (IS_ALIGNED(pfn, PAGES_PER_SECTION))
 			cond_resched();
-- 
2.20.1

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

* [PATCH v7 5/9] mm: extend the template fast path to zone-device compound tails
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
                   ` (4 preceding siblings ...)
  2026-07-20 12:02 ` [PATCH v7 4/9] mm: add a template-based fast path for zone-device page init Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 6/9] string: introduce memcpy_nontemporal() Li Zhe
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

The template fast path from the previous patch only accelerates head
pages. Compound tails in memmap_init_compound() still go through the
zone_device_page_init_slow() one by one.

Build separate head and tail templates and reuse one prepared tail
template across the tail pages in a compound range. Head pages preserve
the existing refcount policy, while compound tails always start with a
refcount of 0 after prep_compound_tail().

This extends the template-copy fast path to pfns_per_compound > 1
without changing the existing zone_device_page_init_slow() helper.
Tail-page PFN-dependent fields are refreshed in the reusable tail
template before each copy.

Tested in a VM with a 100 GB devdax namespace (align=2097152) on Intel
Ice Lake server. This test exercises the dax_pmem rebind path and
measures memmap initialization latency.

Test procedure:
Unbind and rebind the dax_pmem driver 30 times, collect memmap
initialization time from the pr_debug() output of memmap_init_zone_device().

Base(v7.2-rc1):
  First binding: 1462 ms
  Average of subsequent rebinds: 273.31 ms

With this patch and its prerequisites applied:
  First binding: 1403 ms
  Average of subsequent rebinds: 244.37 ms

This reduces the average memmap initialization time measured during rebind
from 273.31 ms to 244.37 ms, or about 10.6%.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 mm/mm_init.c | 40 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 35 insertions(+), 5 deletions(-)

diff --git a/mm/mm_init.c b/mm/mm_init.c
index c2645b9bcef3..2668acef4fe6 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1074,6 +1074,16 @@ static inline bool zone_device_page_init_optimization_enabled(void)
 	return !page_ref_tracepoint_active(page_ref_set);
 }
 
+static inline void zone_device_tail_page_init(struct page *page,
+		unsigned long pfn, unsigned long zone_idx, int nid,
+		struct dev_pagemap *pgmap, const struct page *head,
+		unsigned int order)
+{
+	zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
+	prep_compound_tail(page, head, order);
+	set_page_count(page, 0);
+}
+
 /*
  * 'template' is a reusable page prototype rather than a strictly immutable
  * object. Most ZONE_DEVICE fields stay constant across the pages covered by
@@ -1131,10 +1141,12 @@ static void __ref memmap_init_compound(struct page *head,
 				       unsigned long head_pfn,
 				       unsigned long zone_idx, int nid,
 				       struct dev_pagemap *pgmap,
-				       unsigned long nr_pages)
+				       unsigned long nr_pages,
+				       bool use_template)
 {
 	unsigned long pfn, end_pfn = head_pfn + nr_pages;
 	unsigned int order = pgmap->vmemmap_shift;
+	struct page template;
 
 	/*
 	 * We have to initialize the pages, including setting up page links.
@@ -1143,12 +1155,29 @@ static void __ref memmap_init_compound(struct page *head,
 	 * the pages in the same go.
 	 */
 	__SetPageHead(head);
+
 	for (pfn = head_pfn + 1; pfn < end_pfn; pfn++) {
 		struct page *page = pfn_to_page(pfn);
 
-		zone_device_page_init_slow(page, pfn, zone_idx, nid, pgmap);
-		prep_compound_tail(page, head, order);
-		set_page_count(page, 0);
+		if (!use_template) {
+			zone_device_tail_page_init(page, pfn, zone_idx, nid,
+						   pgmap, head, order);
+		} else if (pfn == head_pfn + 1) {
+			/*
+			 * All tails of the same compound page share the
+			 * state established by prep_compound_tail(). Reuse
+			 * one tail template for the whole range and
+			 * refresh only the PFN-dependent fields in that
+			 * template before each copy.
+			 */
+			zone_device_tail_page_init(page, pfn, zone_idx, nid,
+						   pgmap, head, order);
+			/* init template page */
+			memcpy(&template, page, sizeof(*page));
+		} else {
+			zone_device_page_init_from_template(page, pfn,
+							    &template);
+		}
 	}
 	prep_compound_head(head, order);
 }
@@ -1210,7 +1239,8 @@ void __ref memmap_init_zone_device(struct zone *zone,
 			continue;
 
 		memmap_init_compound(page, pfn, zone_idx, nid, pgmap,
-				     compound_nr_pages(pfn, altmap, pgmap));
+				     compound_nr_pages(pfn, altmap, pgmap),
+				     use_template);
 	}
 
 	pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE);
-- 
2.20.1

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

* [PATCH v7 6/9] string: introduce memcpy_nontemporal()
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
                   ` (5 preceding siblings ...)
  2026-07-20 12:02 ` [PATCH v7 5/9] mm: extend the template fast path to zone-device compound tails Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 7/9] x86/string: extend memcpy_flushcache() fixed-size fastpaths Li Zhe
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

Introduce memcpy_nontemporal() for write-once copy sites that want a
named non-temporal copy primitive.

On x86_64, override the helper in arch/x86/include/asm/string_64.h using
the usual self-macro pattern, next to the existing memcpy_flushcache()
backend that memcpy_nontemporal() wraps.

include/linux/string.h provides the generic memcpy_nontemporal()
fallback as

  #define memcpy_nontemporal memcpy

instead of an inline wrapper, so architectures without a specialized
backend keep the usual memcpy() FORTIFY coverage when the compiler can
still see object sizes at the original call site.

The immediate user is the ZONE_DEVICE template-copy path. It populates
struct page descriptors in a write-once pattern, so a regular cached
memcpy() can incur avoidable write-allocate traffic and cache pollution
for data with little near-term reuse.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 arch/x86/include/asm/string_64.h | 12 ++++++++++++
 include/linux/string.h           | 12 ++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/arch/x86/include/asm/string_64.h b/arch/x86/include/asm/string_64.h
index 4635616863f5..21ae515ae35a 100644
--- a/arch/x86/include/asm/string_64.h
+++ b/arch/x86/include/asm/string_64.h
@@ -100,6 +100,18 @@ static __always_inline void memcpy_flushcache(void *dst, const void *src, size_t
 	}
 	__memcpy_flushcache(dst, src, cnt);
 }
+
+#define memcpy_nontemporal memcpy_nontemporal
+/*
+ * Reuse the existing x86 flushcache backend as the non-temporal copy
+ * primitive.
+ */
+static __always_inline void memcpy_nontemporal(void *dst, const void *src,
+		size_t cnt)
+{
+	memcpy_flushcache(dst, src, cnt);
+}
+
 #endif
 
 #endif /* __KERNEL__ */
diff --git a/include/linux/string.h b/include/linux/string.h
index 5702daca4326..9815c08867d7 100644
--- a/include/linux/string.h
+++ b/include/linux/string.h
@@ -278,6 +278,18 @@ static inline void memcpy_flushcache(void *dst, const void *src, size_t cnt)
 }
 #endif
 
+#ifndef memcpy_nontemporal
+/*
+ * memcpy_nontemporal() requests a non-temporal copy when the
+ * architecture has a suitable backend. Architectures without a
+ * specialized backend fall back to memcpy(). Keep this as a macro alias
+ * so the compiler can still see the original memcpy() call site and
+ * preserve the usual FORTIFY coverage when object sizes remain visible
+ * there.
+ */
+#define memcpy_nontemporal memcpy
+#endif
+
 void *memchr_inv(const void *s, int c, size_t n);
 char *strreplace(char *str, char old, char new);
 
-- 
2.20.1

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

* [PATCH v7 7/9] x86/string: extend memcpy_flushcache() fixed-size fastpaths
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
                   ` (6 preceding siblings ...)
  2026-07-20 12:02 ` [PATCH v7 6/9] string: introduce memcpy_nontemporal() Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 8/9] mm: use memcpy_nontemporal() in zone-device template copies Li Zhe
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

The new x86 memcpy_nontemporal() helper in this series maps to
memcpy_flushcache(), and the ZONE_DEVICE fast path uses that primitive
for constant-sized struct page template copies.

The immediately relevant x86_64 sizeof(struct page) values here are 64,
80, and 96 bytes. memcpy_flushcache() currently inlines only the 4, 8,
and 16-byte cases, so even those constant-sized struct page copies fall
through to __memcpy_flushcache().

Add 32, 48, 64, 80, and 96-byte MOVNTI sequences to the main fixed-size
switch so those constant-sized copies can stay on the inline path.
Factor the larger sequences into movnti_8()/16()/32()/64() helpers so
the switch can reuse them without duplicating the inline assembly.
While the ZONE_DEVICE template-copy path only needs 64/80/96-byte
copies, keep 32-byte and 48-byte copies inline as well rather than
sending those nearby fixed-size cases back to __memcpy_flushcache().

A microbenchmark compared two memcpy_flushcache()-style helpers that
shared the same generic body and differed only in whether
32/48/64/80/96-byte copies stayed in the fixed-size switch or were
redirected to __memcpy_flushcache() when the destination was not 8-byte
aligned. The timed interval covered the copy loop only; wmb() was used
only to separate rounds.

In pseudo-code, the two variants were:

variant A:
	switch (len) {
		case 32:
		case 48:
		case 64:
		case 80:
		case 96:
			do fixed-size MOVNTI stores;
			return;
		default:
			generic __memcpy_flushcache()-style body;
	}

variant B:
	switch (len) {
		case 32:
		case 48:
		case 64:
		case 80:
		case 96:
			if (!IS_ALIGNED(dst, 8))
				goto generic_path;
			do fixed-size MOVNTI stores;
			return;
		default:
	generic_path:
			generic __memcpy_flushcache()-style body;
	}

For offsets 1..7 averaged, keeping those sizes in the fixed-size
switch took about 0.410/0.411/0.426/0.426/0.428 us per
32/48/64/80/96-byte copy, while redirecting the same cases to
__memcpy_flushcache() took about 0.962/0.956/0.923/0.998/1.001 us.

Based on that measurement, keep these new cases in the main
memcpy_flushcache() fixed-size switch without a separate
alignment check.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 arch/x86/include/asm/string_64.h | 56 +++++++++++++++++++++++++++++++-
 1 file changed, 55 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/string_64.h b/arch/x86/include/asm/string_64.h
index 21ae515ae35a..bc6a9f34b346 100644
--- a/arch/x86/include/asm/string_64.h
+++ b/arch/x86/include/asm/string_64.h
@@ -82,7 +82,35 @@ int strcmp(const char *cs, const char *ct);
 #ifdef CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE
 #define __HAVE_ARCH_MEMCPY_FLUSHCACHE 1
 void __memcpy_flushcache(void *dst, const void *src, size_t cnt);
-static __always_inline void memcpy_flushcache(void *dst, const void *src, size_t cnt)
+
+static __always_inline void movnti_8(void *dst, const void *src)
+{
+	asm volatile("movntiq %1, %0"
+		     : "=m"(*(u64 *)dst)
+		     : "r"(*(const u64 *)src)
+		     : "memory");
+}
+
+static __always_inline void movnti_16(void *dst, const void *src)
+{
+	movnti_8(dst, src);
+	movnti_8(dst + 8, src + 8);
+}
+
+static __always_inline void movnti_32(void *dst, const void *src)
+{
+	movnti_16(dst, src);
+	movnti_16(dst + 16, src + 16);
+}
+
+static __always_inline void movnti_64(void *dst, const void *src)
+{
+	movnti_32(dst, src);
+	movnti_32(dst + 32, src + 32);
+}
+
+static __always_inline void memcpy_flushcache(void *dst, const void *src,
+					      size_t cnt)
 {
 	if (__builtin_constant_p(cnt)) {
 		switch (cnt) {
@@ -96,8 +124,34 @@ static __always_inline void memcpy_flushcache(void *dst, const void *src, size_t
 				asm ("movntiq %1, %0" : "=m"(*(u64 *)dst) : "r"(*(u64 *)src));
 				asm ("movntiq %1, %0" : "=m"(*(u64 *)(dst + 8)) : "r"(*(u64 *)(src + 8)));
 				return;
+			/*
+			 * The relevant fixed-size copies here are the
+			 * x86_64 struct page sizes: 64, 80, and 96 bytes.
+			 * Keep 32-byte and 48-byte copies inline as well
+			 * instead of sending those nearby fixed-size
+			 * cases back to __memcpy_flushcache().
+			 */
+			case 32:
+				movnti_32(dst, src);
+				return;
+			case 48:
+				movnti_32(dst, src);
+				movnti_16(dst + 32, src + 32);
+				return;
+			case 64:
+				movnti_64(dst, src);
+				return;
+			case 80:
+				movnti_64(dst, src);
+				movnti_16(dst + 64, src + 64);
+				return;
+			case 96:
+				movnti_64(dst, src);
+				movnti_32(dst + 64, src + 64);
+				return;
 		}
 	}
+
 	__memcpy_flushcache(dst, src, cnt);
 }
 
-- 
2.20.1

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

* [PATCH v7 8/9] mm: use memcpy_nontemporal() in zone-device template copies
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
                   ` (7 preceding siblings ...)
  2026-07-20 12:02 ` [PATCH v7 7/9] x86/string: extend memcpy_flushcache() fixed-size fastpaths Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 12:02 ` [PATCH v7 9/9] mm: always use the zone-device template init path Li Zhe
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

The template fast path currently uses memcpy() for the actual struct
page copy. Switch zone_device_page_init_from_template() to
memcpy_nontemporal().

ZONE_DEVICE memmap initialization is largely write-once: each struct
page is populated once, and most destination cachelines are not expected
to be reused immediately afterwards. On x86, a regular cached memcpy()
can therefore incur write-allocate traffic by pulling destination
cachelines into the cache before writeback, and can populate the cache
with data that has little near-term reuse. Using memcpy_nontemporal()
lets this path request nontemporal stores for that copy pattern, which
can reduce cache pollution and avoid part of the associated
write-allocate overhead, while architectures without a specialized
backend still fall back to memcpy().

No separate drain is added here. memcpy_nontemporal() is used only as
the copy primitive while memmap_init_zone_device() is still initializing
the struct page array. The ordinary stores that follow in this path,
such as compound-page setup, are part of the same CPU's initialization
sequence; they are not used as a publication store that tells another CPU
or device to consume data written by the non-temporal copy.

Therefore this call site does not need a helper-level drain for
correctness. Callers that use memcpy_nontemporal() as part of a
producer-consumer or device-visible handoff must add the required
ordering themselves.

At this point, sanitized builds still use the slow path so KASAN/KMSAN
retain their instrumented stores. The following cleanup removes that
local opt-out together with the remaining non-template fallback.

Tested in a VM with a 100 GB fsdax namespace device configured with
map=dev and a 100 GB devdax namespace (align=2097152) on Intel Ice Lake
server.

Test procedure:
Rebind the nd_pmem and dax_pmem driver 30 times and collect the memmap
initialization time from the pr_debug() output of
memmap_init_zone_device().

Base(v7.2-rc1):
  First binding for nd_pmem driver: 1456 ms
  Average of subsequent rebinds: 244.28 ms

  First binding for dax_pmem driver: 1462 ms
  Average of subsequent rebinds: 273.31 ms

With this series:
  First binding for nd_pmem driver: 1272 ms
  Average of subsequent rebinds: 96.79 ms

  First binding for dax_pmem driver: 1354 ms
  Average of subsequent rebinds: 119.04 ms

This reduces the average memmap initialization time measured during rebind
by about 60.4% for nd_pmem and 56.4% for dax_pmem.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 mm/mm_init.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/mm/mm_init.c b/mm/mm_init.c
index 2668acef4fe6..2a723a518f41 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1067,11 +1067,21 @@ static void __ref zone_device_page_init_slow(struct page *page,
 
 static inline bool zone_device_page_init_optimization_enabled(void)
 {
+	/*
+	 * Keep sanitized builds on the slow path so their stores stay
+	 * instrumented.
+	 */
+	if (IS_ENABLED(CONFIG_KASAN) || IS_ENABLED(CONFIG_KMSAN))
+		return false;
+
 	/*
 	 * The template fast path copies a preinitialized struct page image.
 	 * Skip it when the page_ref_set tracepoint is enabled.
 	 */
-	return !page_ref_tracepoint_active(page_ref_set);
+	if (page_ref_tracepoint_active(page_ref_set))
+		return false;
+
+	return true;
 }
 
 static inline void zone_device_tail_page_init(struct page *page,
@@ -1110,7 +1120,7 @@ static void zone_device_page_init_from_template(struct page *page,
 	 * to the destination page.
 	 */
 	zone_device_page_update_template(template, pfn);
-	memcpy(page, template, sizeof(*page));
+	memcpy_nontemporal(page, template, sizeof(*page));
 }
 
 /*
-- 
2.20.1

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

* [PATCH v7 9/9] mm: always use the zone-device template init path
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
                   ` (8 preceding siblings ...)
  2026-07-20 12:02 ` [PATCH v7 8/9] mm: use memcpy_nontemporal() in zone-device template copies Li Zhe
@ 2026-07-20 12:02 ` Li Zhe
  2026-07-20 13:05 ` [PATCH v7 0/9] mm: optimize zone-device memmap initialization Dave Hansen
  2026-07-20 22:43 ` Andrew Morton
  11 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-20 12:02 UTC (permalink / raw)
  To: akpm, apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo,
	rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86,
	lizhe.67

The template path still has a local opt-out predicate for the
page_ref_set tracepoint and keeps a non-template fallback path. This
makes the code more complex without a strong reason to keep the
separate path.

Use the template path unconditionally after the corresponding seed page
has been initialized: the first head page seeds the head template, and
the first tail page in each compound range seeds the tail template.

This keeps the first-page seeding logic unchanged, but removes the extra
non-template fallback from the main loops.

This also means sanitized builds no longer force this path back to the
per-page initialization stores, and an enabled page_ref_set tracepoint
will no longer observe every ZONE_DEVICE initialization-time refcount
assignment. That tradeoff is intentional: this code is still initializing
struct pages before they are handed out, and keeping those local debug
special cases would preserve a separate initialization-only path while
making the common code more complex.

Suggested-by: Muchun Song <muchun.song@linux.dev>
Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 mm/mm_init.c | 36 ++++--------------------------------
 1 file changed, 4 insertions(+), 32 deletions(-)

diff --git a/mm/mm_init.c b/mm/mm_init.c
index 2a723a518f41..ae42a1311e1d 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -1065,25 +1065,6 @@ static void __ref zone_device_page_init_slow(struct page *page,
 		set_page_count(page, 0);
 }
 
-static inline bool zone_device_page_init_optimization_enabled(void)
-{
-	/*
-	 * Keep sanitized builds on the slow path so their stores stay
-	 * instrumented.
-	 */
-	if (IS_ENABLED(CONFIG_KASAN) || IS_ENABLED(CONFIG_KMSAN))
-		return false;
-
-	/*
-	 * The template fast path copies a preinitialized struct page image.
-	 * Skip it when the page_ref_set tracepoint is enabled.
-	 */
-	if (page_ref_tracepoint_active(page_ref_set))
-		return false;
-
-	return true;
-}
-
 static inline void zone_device_tail_page_init(struct page *page,
 		unsigned long pfn, unsigned long zone_idx, int nid,
 		struct dev_pagemap *pgmap, const struct page *head,
@@ -1151,8 +1132,7 @@ static void __ref memmap_init_compound(struct page *head,
 				       unsigned long head_pfn,
 				       unsigned long zone_idx, int nid,
 				       struct dev_pagemap *pgmap,
-				       unsigned long nr_pages,
-				       bool use_template)
+				       unsigned long nr_pages)
 {
 	unsigned long pfn, end_pfn = head_pfn + nr_pages;
 	unsigned int order = pgmap->vmemmap_shift;
@@ -1169,10 +1149,7 @@ static void __ref memmap_init_compound(struct page *head,
 	for (pfn = head_pfn + 1; pfn < end_pfn; pfn++) {
 		struct page *page = pfn_to_page(pfn);
 
-		if (!use_template) {
-			zone_device_tail_page_init(page, pfn, zone_idx, nid,
-						   pgmap, head, order);
-		} else if (pfn == head_pfn + 1) {
+		if (pfn == head_pfn + 1) {
 			/*
 			 * All tails of the same compound page share the
 			 * state established by prep_compound_tail(). Reuse
@@ -1197,7 +1174,6 @@ void __ref memmap_init_zone_device(struct zone *zone,
 				   unsigned long nr_pages,
 				   struct dev_pagemap *pgmap)
 {
-	bool use_template = zone_device_page_init_optimization_enabled();
 	unsigned long pfn, end_pfn = start_pfn + nr_pages;
 	struct pglist_data *pgdat = zone->zone_pgdat;
 	struct vmem_altmap *altmap = pgmap_altmap(pgmap);
@@ -1223,10 +1199,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
 	for (pfn = start_pfn; pfn < end_pfn; pfn += pfns_per_compound) {
 		struct page *page = pfn_to_page(pfn);
 
-		if (!use_template) {
-			zone_device_page_init_slow(page, pfn, zone_idx,
-						   nid, pgmap);
-		} else if (pfn == start_pfn) {
+		if (pfn == start_pfn) {
 			/*
 			 * Seed the reusable head-page template from the
 			 * first real struct page, because the existing
@@ -1249,8 +1222,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
 			continue;
 
 		memmap_init_compound(page, pfn, zone_idx, nid, pgmap,
-				     compound_nr_pages(pfn, altmap, pgmap),
-				     use_template);
+				     compound_nr_pages(pfn, altmap, pgmap));
 	}
 
 	pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE);
-- 
2.20.1

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

* Re: [PATCH v7 0/9] mm: optimize zone-device memmap initialization
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
                   ` (9 preceding siblings ...)
  2026-07-20 12:02 ` [PATCH v7 9/9] mm: always use the zone-device template init path Li Zhe
@ 2026-07-20 13:05 ` Dave Hansen
  2026-07-22  4:00   ` Li Zhe
  2026-07-20 22:43 ` Andrew Morton
  11 siblings, 1 reply; 15+ messages in thread
From: Dave Hansen @ 2026-07-20 13:05 UTC (permalink / raw)
  To: Li Zhe, akpm, apopple, arnd, balbirs, bp, dave.hansen, david,
	kees, mingo, rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86

On 7/20/26 05:02, Li Zhe wrote:
> This reduces the average memmap initialization time measured during
> rebind by about 60.4% for nd_pmem and 56.4% for dax_pmem.

There are lots of things in the kernel we could optimize.

Why is this important?

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

* Re: [PATCH v7 0/9] mm: optimize zone-device memmap initialization
  2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
                   ` (10 preceding siblings ...)
  2026-07-20 13:05 ` [PATCH v7 0/9] mm: optimize zone-device memmap initialization Dave Hansen
@ 2026-07-20 22:43 ` Andrew Morton
  2026-07-22  8:18   ` Li Zhe
  11 siblings, 1 reply; 15+ messages in thread
From: Andrew Morton @ 2026-07-20 22:43 UTC (permalink / raw)
  To: Li Zhe
  Cc: apopple, arnd, balbirs, bp, dave.hansen, david, kees, mingo, rppt,
	tglx, muchun.song, linux-arch, linux-hardening, linux-kernel,
	linux-mm, x86

On Mon, 20 Jul 2026 20:02:49 +0800 "Li Zhe" <lizhe.67@bytedance.com> wrote:

> memmap_init_zone_device() can take a noticeable amount of time when large
> pmem namespaces are bound or rebound, because it initializes nearly
> identical struct page descriptors one PFN at a time. This series reduces
> that ZONE_DEVICE memmap initialization overhead by reusing prepared
> struct page templates and, on x86, using memcpy_nontemporal() for the
> template copy path.

What Dave said - more details on the real-world userspace benefit would
be helpful, thanks.

AI review might have found some things, one "critical":
	https://sashiko.dev/#/patchset/20260720120259.1545-1-lizhe.67@bytedance.com

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

* Re: [PATCH v7 0/9] mm: optimize zone-device memmap initialization
  2026-07-20 13:05 ` [PATCH v7 0/9] mm: optimize zone-device memmap initialization Dave Hansen
@ 2026-07-22  4:00   ` Li Zhe
  0 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-22  4:00 UTC (permalink / raw)
  To: Dave Hansen, akpm, apopple, arnd, balbirs, bp, dave.hansen, david,
	kees, mingo, rppt, tglx, muchun.song
  Cc: linux-arch, linux-hardening, linux-kernel, linux-mm, x86

On 7/20/26 9:05 PM, Dave Hansen wrote:
> On 7/20/26 05:02, Li Zhe wrote:
>> This reduces the average memmap initialization time measured during
>> rebind by about 60.4% for nd_pmem and 56.4% for dax_pmem.
> There are lots of things in the kernel we could optimize.
>
> Why is this important?
This is in the synchronous probe/bind path for large DAX/PMEM
ZONE_DEVICE mappings. Userspace operations such as enabling or
reconfiguring nd_pmem/dax_pmem namespaces, or bringing a hot-added PMEM
device online, will benefit from the lower synchronous initialization
latency.

The numbers in the cover letter are only for memmap_init_zone_device(),
not for the full driver bind operation. But that time is still a
blocking part of the user-visible operation. On the 100 GB setup it is
already a few hundred milliseconds per bind/rebind, and the cost grows
with the amount of ZONE_DEVICE memmap initialized. With larger devices,
or several namespaces being brought online, that becomes visible
provisioning and recovery latency.

So the benefit is lower latency for provisioning, hot-add, and recovery
or rebind of large DAX/PMEM devices.

Thanks,
Zhe

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

* Re: [PATCH v7 0/9] mm: optimize zone-device memmap initialization
  2026-07-20 22:43 ` Andrew Morton
@ 2026-07-22  8:18   ` Li Zhe
  0 siblings, 0 replies; 15+ messages in thread
From: Li Zhe @ 2026-07-22  8:18 UTC (permalink / raw)
  To: akpm
  Cc: apopple, arnd, balbirs, bp, dave.hansen, david, kees, linux-arch,
	linux-hardening, linux-kernel, linux-mm, lizhe.67, mingo,
	muchun.song, rppt, tglx, x86

On Mon, 20 Jul 2026 15:43:34 -0700, akpm@linux-foundation.org wrote:

> AI review might have found some things, one "critical":
> 	https://sashiko.dev/#/patchset/20260720120259.1545-1-lizhe.67@bytedance.com
> 
> --- Patch [6]: [PATCH v7 6/9] string: introduce memcpy_nontemporal() ---
> commit 48cf815dd31950a95bb4752e1e8e62594a090364
> Author: Li Zhe <lizhe.67@bytedance.com>
> string: introduce memcpy_nontemporal()
> Introduce memcpy_nontemporal() for write-once copy sites that want a
> named non-temporal copy primitive.
> > diff --git a/arch/x86/include/asm/string_64.h b/arch/x86/include/asm/string_64.h
> > index 4635616863f53..21ae515ae35a3 100644
> > --- a/arch/x86/include/asm/string_64.h
> > +++ b/arch/x86/include/asm/string_64.h
> > @@ -100,6 +100,18 @@ static __always_inline void memcpy_flushcache(void *dst, const void *src, size_t
> >  	}
> >  	__memcpy_flushcache(dst, src, cnt);
> >  }
> > +
> > +#define memcpy_nontemporal memcpy_nontemporal
> > +/*
> > + * Reuse the existing x86 flushcache backend as the non-temporal copy
> > + * primitive.
> > + */
> > +static __always_inline void memcpy_nontemporal(void *dst, const void *src,
> > +		size_t cnt)
> > +{
> > +	memcpy_flushcache(dst, src, cnt);
> > +}
> Does this missing fencing introduce a memory ordering hazard on x86_64?
> Because memcpy_flushcache() uses weakly-ordered movnti instructions, it
> bypasses the x86 Total Store Order (TSO) guarantees. If generic code
> follows memcpy_nontemporal() with smp_store_release() or smp_wmb(), it
> could result in data corruption since those standard SMP barriers only
> compile to compiler barriers on x86_64.
> Is there a plan to add a built-in sfence here, or update the
> documentation to warn callers that wmb() is strictly required?

On x86 it intentionally follows the existing memcpy_flushcache() style:
it performs the non-temporal copy, but it does not imply a drain or a
publication barrier.

Callers that use memcpy_nontemporal() as part of a producer-consumer or
device-visible handoff must add the required ordering at the handoff
point. On x86, that means a real write barrier such as wmb() or pmem_wmb();
smp_wmb() and smp_store_release() are not sufficient for ordering
non-temporal stores before such a handoff.

This patchset does not need such a drain in the ZONE_DEVICE template-copy
path. The copy is followed by more local struct page initialization on
the same CPU, not by a publication store to another CPU or device.

> Will this bypass KASAN memory safety instrumentation and FORTIFY_SOURCE
> bounds checking on x86_64?
> The inline wrapper doesn't use __FORTIFY_INLINE, and calling
> memcpy_flushcache() directly skips the usual kasan_check_write() and
> kasan_check_read() protections that the generic memcpy macro preserves.

On x86, memcpy_nontemporal() is only a thin wrapper around the existing
memcpy_flushcache() backend. I would prefer to keep the wrapper at that
level and not add a separate FORTIFY_SOURCE layer here, since the backend
it wraps does not provide one either.

For this series, bypassing KASAN/KMSAN instrumentation in the final
ZONE_DEVICE template-copy path is expected and is called out in the final
cleanup patch.

> 
> > diff --git a/include/linux/string.h b/include/linux/string.h
> > index 5702daca4326b..9815c08867d72 100644
> > --- a/include/linux/string.h
> > +++ b/include/linux/string.h
> [ ... ]
> > @@ -278,6 +278,18 @@ static inline void memcpy_flushcache(void *dst, const void *src, size_t cnt)
> >  }
> >  #endif
> >  
> > +#ifndef memcpy_nontemporal
> > +/*
> > + * memcpy_nontemporal() requests a non-temporal copy when the
> > + * architecture has a suitable backend. Architectures without a
> > + * specialized backend fall back to memcpy(). Keep this as a macro alias
> > + * so the compiler can still see the original memcpy() call site and
> > + * preserve the usual FORTIFY coverage when object sizes remain visible
> > + * there.
> > + */
> > +#define memcpy_nontemporal memcpy
> > +#endif
> Could this macro definition cause build failures due to a return type
> mismatch across architectures?
> The generic fallback here aliases to memcpy, which implicitly returns
> void *, while the x86_64 backend explicitly returns void. If a caller
> attempts to use the return value on x86_64, the build will fail with a
> void value error, whereas it will compile cleanly on fallback
> architectures.
> Should this macro cast the result to void to enforce a uniform API?
> For example: #define memcpy_nontemporal(d, s, l) ((void)memcpy(d, s, l))

Agreed. The return value should not be part of the memcpy_nontemporal()
API. I will make the generic fallback a void function-like macro. This
keeps the API consistent with memcpy_flushcache(), which also returns
void, and makes accidental use of a return value fail consistently
across architectures.

> --- Patch [7]: [PATCH v7 7/9] x86/string: extend memcpy_flushcache() fixed-size fastpaths ---
> commit 882d29aec39f30f4e6b01548ec298e08ca1ab787
> Author: Li Zhe <lizhe.67@bytedance.com>
> x86/string: extend memcpy_flushcache() fixed-size fastpaths
> This commit adds larger fixed-size fast paths (32 to 96 bytes) to
> memcpy_flushcache() using inline movnti helpers. It also explicitly removes
> the 8-byte destination alignment check for these sizes based on microbenchmark
> results showing improved performance for unaligned copies.
> > diff --git a/arch/x86/include/asm/string_64.h b/arch/x86/include/asm/string_64.h
> > --- a/arch/x86/include/asm/string_64.h
> > +++ b/arch/x86/include/asm/string_64.h
> [ ... ]
> > +static __always_inline void movnti_8(void *dst, const void *src)
> > +{
> > +	asm volatile("movntiq %1, %0"
> > +		     : "=m"(*(u64 *)dst)
> > +		     : "r"(*(const u64 *)src)
> Does casting an unaligned void pointer to a strictly-aligned u64 pointer and
> dereferencing it invoke undefined behavior here?
> Since this fast path handles unaligned addresses, the compiler might assume the
> memory operand is naturally 8-byte aligned based on the u64 type. Could this
> allow the compiler to incorrectly optimize away subsequent alignment checks or
> emit invalid aligned optimizations?

I do not see an 8-byte alignment requirement for MOVNTI in the Intel SDM.
This also matches the existing memcpy_flushcache() fixed-size cases: the
8-byte and 16-byte cases already use MOVNTI without a separate alignment
check.

So I do not think this series needs to add an alignment fallback for the
new 32/48/64/80/96-byte fixed-size cases. Please correct me if I am wrong.

> > +		     : "memory");
> Is this global "memory" clobber necessary given the specific "=m" output
> constraint?
> When this helper is nested to create larger copies like movnti_64(), it
> generates up to 8 consecutive full compiler memory barriers. Does this force
> the compiler to unnecessarily spill and reload locally cached variables from
> registers, preventing instruction scheduling across each 8-byte chunk?

The "memory" clobber is intentional here. The goal is to keep the
fixed-size copy as a compact sequence of MOVNTI stores and avoid the
compiler moving other memory accesses into the middle of that streaming
store sequence. The "=m" operand describes the destination memory
operand, but it does not express that broader scheduling constraint.

The largest fixed-size copy added by this patch is still small, and the
measured result for the final memcpy_flushcache() path is still favorable
for the ZONE_DEVICE struct page copies that motivated this series. So I
would prefer to keep the conservative clobber in this version unless
there is a demonstrated code generation or performance regression from
it. Please correct me if I am wrong.

> > +}
> [ ... ]
> > +static __always_inline void memcpy_flushcache(void *dst, const void *src,
> > +					      size_t cnt)
> >  {
> [ ... ]
> > +			case 32:
> > +				movnti_32(dst, src);
> > +				return;
> Does skipping the 8-byte alignment check here break the cache bypass guarantee
> required for persistent memory (PMEM) durability?
> The memcpy_flushcache() API guarantees that written data bypasses the CPU cache
> so PMEM updates are durable without requiring manual clflush. On x86, unaligned
> non-temporal stores (MOVNTI) crossing cache lines silently fall back to
> temporal Read-For-Ownership cache allocations.
> Because this inline fast path bypasses __memcpy_flushcache() (which correctly
> aligns the destination using normal memcpy and clflushopt), will the unaligned
> data silently sit in the CPU L1/L2 cache instead of reaching the PMEM module?
> Is it possible that the microbenchmark speedup mentioned in the commit message
> (0.410us vs 0.962us for unaligned offsets) was achieved precisely because the
> stores hit the fast L1 cache instead of actually flushing to memory?

I do not see anything in the Intel SDM saying that an unaligned
non-temporal MOVNTI store that crosses a cache line silently falls back
to temporal RFO cache allocations. The existing 4/8/16-byte
memcpy_flushcache() fixed-size cases already use MOVNTI without such an
alignment check. Please correct me if I am wrong.

The microbenchmark is only used as the performance motivation for keeping
these small constant-size copies in the inline path. It is not intended
as a durability proof.

> --- Patch [8]: [PATCH v7 8/9] mm: use memcpy_nontemporal() in zone-device template copies ---
> commit ebc6b81ea2e4b12a555515b9468c89fd0bdfe4f1
> Author: Li Zhe <lizhe.67@bytedance.com>
> mm: use memcpy_nontemporal() in zone-device template copies
> The template fast path currently uses memcpy() for the actual struct page
> copy. Switch zone_device_page_init_from_template() to memcpy_nontemporal()
> to reduce write-allocate cache traffic.
> > diff --git a/mm/mm_init.c b/mm/mm_init.c
> > index 2668acef4fe60..2a723a518f413 100644
> > --- a/mm/mm_init.c
> > +++ b/mm/mm_init.c
> [ ... ]
> > @@ -1110,7 +1120,7 @@ static void zone_device_page_init_from_template(struct page *page,
> >  	 * to the destination page.
> >  	 */
> >  	zone_device_page_update_template(template, pfn);
> > -	memcpy(page, template, sizeof(*page));
> > +	memcpy_nontemporal(page, template, sizeof(*page));
> Could this mix of non-temporal and temporal stores to the same memory location
> cause data corruption on x86?
> During DAX device binding, memmap_init_zone_device() copies the template to
> the head page using this memcpy_nontemporal() call, and then immediately
> calls memmap_init_compound():
> mm/mm_init.c:memmap_init_zone_device() {
>     ...
> 		} else {
> 			zone_device_page_init_from_template(page, pfn,
> 							    &template);
> 		}
>     ...
> 		memmap_init_compound(page, pfn, zone_idx, nid, pgmap,
> 				     compound_nr_pages(pfn, altmap, pgmap),
> 				     use_template);
>     ...
> }
> Inside memmap_init_compound(), a normal temporal store is applied to the
> head page:
> mm/mm_init.c:memmap_init_compound() {
>     ...
> 	/*
> 	 * We have to initialize the pages, including setting up page links.
> 	 * prep_compound_page() does not take care of that, so instead we
> 	 * open-code prep_compound_page() so we can take care of initializing
> 	 * the pages in the same go.
> 	 */
> 	__SetPageHead(head);
>     ...
> }
> The commit message states:
> "The ordinary stores that follow in this path, such as compound-page setup,
> are part of the same CPU's initialization sequence; they are not used as a
> publication store that tells another CPU or device to consume data written
> by the non-temporal copy. Therefore this call site does not need a helper-
> level drain for correctness."
> However, because x86 does not maintain coherency between write-combining (WC)
> buffers and temporal cache lines for the same address, a pending WC buffer
> flush can unpredictably overwrite the L1 cache updates from the same CPU. This
> could cause the PG_head bit to be lost.
> Does this path actually require an intervening memory barrier (like sfence)
> before the temporal writes to guarantee correctness?

I do not think an sfence is required between the template copy and the
following compound-page setup.

The weak-ordering concern for non-temporal stores is about when those
stores become visible to other agents, and about ordering them before an
external handoff. The following __SetPageHead() store is not such a
handoff. It is still part of the same CPU's construction of the same
struct page metadata.

I do not think the x86 memory-ordering rules imply that a later
same-CPU temporal update to the same struct page can be silently lost
behind an earlier non-temporal store. The required fence is for ordering
non-temporal stores before an external observer or handoff.

So the relevant ordering point would be before publishing data written by
non-temporal stores to another CPU or device. This path does not do that
between the memcpy_nontemporal() call and the following compound-page
stores. It is only continuing local initialization of the struct page.

Thanks,
Zhe

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

end of thread, other threads:[~2026-07-22  8:18 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20 12:02 [PATCH v7 0/9] mm: optimize zone-device memmap initialization Li Zhe
2026-07-20 12:02 ` Li Zhe
2026-07-20 12:02 ` [PATCH v7 1/9] mm: fix stale ZONE_DEVICE refcount comment Li Zhe
2026-07-20 12:02 ` [PATCH v7 2/9] mm: factor zone-device page init helpers out of __init_zone_device_page Li Zhe
2026-07-20 12:02 ` [PATCH v7 3/9] mm: add a set_page_section_from_pfn() helper Li Zhe
2026-07-20 12:02 ` [PATCH v7 4/9] mm: add a template-based fast path for zone-device page init Li Zhe
2026-07-20 12:02 ` [PATCH v7 5/9] mm: extend the template fast path to zone-device compound tails Li Zhe
2026-07-20 12:02 ` [PATCH v7 6/9] string: introduce memcpy_nontemporal() Li Zhe
2026-07-20 12:02 ` [PATCH v7 7/9] x86/string: extend memcpy_flushcache() fixed-size fastpaths Li Zhe
2026-07-20 12:02 ` [PATCH v7 8/9] mm: use memcpy_nontemporal() in zone-device template copies Li Zhe
2026-07-20 12:02 ` [PATCH v7 9/9] mm: always use the zone-device template init path Li Zhe
2026-07-20 13:05 ` [PATCH v7 0/9] mm: optimize zone-device memmap initialization Dave Hansen
2026-07-22  4:00   ` Li Zhe
2026-07-20 22:43 ` Andrew Morton
2026-07-22  8:18   ` Li Zhe

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