[PATCH v6 0/8] mm: optimize zone-device memmap initialization

"Li Zhe" <[email protected]>
Newsgroups org.kernel.vger.linux-arch,org.kernel.vger.linux-hardening,org.kernel.vger.linux-kernel,org.kvack.linux-mm
Message-ID <[email protected]>
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_nt() 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 fast path for head pages and compound tails.
Patches 6-8 introduce memcpy_nt()/memcpy_nt_drain(), extend the x86
fixed-size memcpy_flushcache() inline cases used by that helper, and
switch the template-copy path over to memcpy_nt().

The fast path remains disabled when the page_ref_set tracepoint is
active, and sanitized builds stay on the slow path so their instrumented
stores are preserved. Architectures without a specialized memcpy_nt()
backend continue to fall back to memcpy().

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 rebind time 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 rebind time 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/[email protected]/

Li Zhe (8):
  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_nt() helpers
  x86/string: extend memcpy_flushcache() fixed-size fastpaths
  mm: use memcpy_nt() in zone-device template copies

 arch/x86/include/asm/string_64.h |  78 ++++++++++++-
 include/linux/mm.h               |  15 ++-
 include/linux/string.h           |  23 ++++
 mm/mm_init.c                     | 186 ++++++++++++++++++++++++++-----
 4 files changed, 273 insertions(+), 29 deletions(-)

---
v5: https://lore.kernel.org/all/[email protected]/
v4: https://lore.kernel.org/all/[email protected]/
v3: https://lore.kernel.org/all/[email protected]/
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/

Changelogs:

v5->v6:
- Rework patch 6 so the x86 memcpy_nt_drain() helper uses wmb()
  instead of an open-coded sfence, while architectures that do not
  override memcpy_nt() keep the generic no-op drain fallback.
  Suggested by Borislav Petkov.
- Rework patch 6 to use the usual self-macro override pattern for
  memcpy_nt() and memcpy_nt_drain() instead of a dedicated
  __HAVE_ARCH_MEMCPY_NT feature macro. Suggested by David
  Hildenbrand.
- Drop the default: case from pagemap_resets_refcount() and drop the
  WARN_ONCE() after the switch so newly added enum memory_type values
  remain visible to compiler switch checking. Suggested by David
  Hildenbrand.
- Drop the unnecessary empty set_page_section() stub from patch 3, keep
  set_page_section_from_pfn() as the only !SECTION_IN_PAGE_FLAGS no-op
  helper and fix its indentation. Suggested by David Hildenbrand.
- Rework patch 4 so the reusable head-page template is seeded from the
  first loop iteration, keeping the main loop intact while ensuring the
  first page is initialized only once; also drop the extra template
  memcpy helper, as suggested by Alistair Popple.
- Rework patch 5 similarly for compound tails: seed the reusable tail
  template from the first tail-page iteration so the first tail page is
  initialized only once, while keeping the main tail loop intact, as
  suggested by Alistair Popple.
- Rework patch 7 to rename the internal fixed-size MOVNTI helpers to
  movnti_*(), keep memcpy_flushcache() as the externally visible
  wrapper, fold the new 32/48/64/80/96-byte copies into the main
  fixed-size switch, clarify in the code and changelog that the
  ZONE_DEVICE template-copy path relies on the 64/80/96-byte struct
  page copies while 32/48-byte copies stay inline as well, and drop the
  separate alignment check based on the physical-host microbenchmark
  results. Suggested by Borislav Petkov.
- Run v6 VM spot checks for the fsdax/devdax map=dev paths. The
  results stayed in the same ballpark as v5, so keep the previously
  posted v5 full-run numbers in this version instead of replacing them
  with another partial data set.

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

-- 
2.20.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.