From: David Laight <david.laight.linux@gmail.com>
To: Shivank Garg <shivankg@amd.com>
Cc: Andrew Morton <akpm@linux-foundation.org>,
David Hildenbrand <david@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>, Borislav Petkov <bp@alien8.de>,
Ankur Arora <ankur.a.arora@oracle.com>,
Dave Hansen <dave.hansen@linux.intel.com>,
"Thomas Gleixner" <tglx@kernel.org>,
Bharata B Rao <bharata@amd.com>,
"Hrushikesh Salunke" <hsalunke@amd.com>,
Ingo Molnar <mingo@redhat.com>, "H . Peter Anvin" <hpa@zytor.com>,
David Rientjes <rientjes@google.com>,
Linus Torvalds <torvalds@linuxfoundation.org>,
"Teja Vojjala" <tejavojjala@google.com>,
Huan Nguyen <nhuhuan@google.com>, <linux-mm@kvack.org>,
<linux-kernel@vger.kernel.org>, <x86@kernel.org>
Subject: Re: [PATCH RFC v2] mm: introduce copy_mc_highpages() and batch copy in folio_mc_copy()
Date: Mon, 19 Oct 2026 13:19:01 +0100 [thread overview]
Message-ID: <20261019131901.7a8fae90@pumpkin> (raw)
In-Reply-To: <20261019-batpagecopy-v2-1-d75e7d0dfaae@amd.com>
On Mon, 19 Oct 2026 10:06:49 +0000
Shivank Garg <shivankg@amd.com> wrote:
> folio_mc_copy() copies a folio one page at a time with copy_mc_highpage(),
> so a 2MB folio makes 512 separate copy_mc_to_kernel() calls. Each page
> pays the startup cost of the string instruction ("REP; MOVSB" on x86
> CPUs with ERMS) plus a cond_resched().
Don't you always want to use 'rep movsq' for these aligned copies (on any
cpu anyone cares about).
I'm pretty even on some that don't advertise ERMS.
The fastest a copy loop is ever going to run (on anything but the latest cpu)
is one write every clock - so 8 bytes/clock.
A 4k page will then take at least 512 clocks.
Even an old sandy bridge cpu will (IIRC) copy 32 bytes/clock if the destination
is 32 byte aligned (not much later cpu double that for 64byte aligned destinations).
So the copy part takes at most 128 clocks.
That makes 'rep movsq' faster unless the fixed overhead is in the P4 netburst
range.
(I was using lfence when I did my performance measurements, it hides
the fixed latency of rep.)
I've never measured zen 1-4.
I also suspect the slow part of a 2M copy is all the D-cache misses
(especially on the read side).
OTOH the longer copy is likely to be better...
David
>
> Introduce copy_mc_highpages(), the page range variant of
> copy_mc_highpage(). When copy_mc_to_kernel() is available and the pages
> are in the linear map, copy the whole range with a single
> copy_mc_to_kernel() call, so the CPU can optimize for the larger extent.
> Otherwise, fall back to copy_mc_highpage() one page at a time.
>
> On a machine check, queue the poisoned source page for memory_failure()
> and return -EHWPOISON, as copy_mc_highpage() does.
>
> copy_mc_to_kernel() cannot reschedule, so copying a large folio with one
> call can run for a long time under cooperative preemption models. Add
> COPY_MC_PAGES_NON_PREEMPT_BATCH to bound how much is copied in one go:
> 32MB, the same bound clear_pages() gets from
> PROCESS_PAGES_NON_PREEMPT_BATCH, or one page on the fallback path.
>
> Switch folio_mc_copy() to copy_mc_highpages(): the whole folio at once
> under preemptible models, COPY_MC_PAGES_NON_PREEMPT_BATCH pages at a time
> otherwise.
>
> Assisted-by: LLM
> Signed-off-by: Shivank Garg <shivankg@amd.com>
> ---
> On a Zen 6 system with CXL memory, move_pages() gets ~1.4x faster for
> 2MB THPs and up to ~2.8x faster for 1GB hugetlb pages (numbers below).
> When demoting to CXL, kswapd uses ~31% less CPU per GB demoted.
>
> Changes in v2:
> - Drop the folio_copy() changes as there is little to gain here.
> On architectures without copy_mc_to_kernel(), copy_mc_highpages()
> falls back to the per-page copy_mc_highpage(), which ends up in
> copy_highpage(). A batched copy_highpages() could help there too,
> but I haven't tested it on those architectures, so that is left for
> a follow-up.
> - Add COPY_MC_PAGES_NON_PREEMPT_BATCH to limit how much is copied
> between cond_resched() calls on non-preemptible kernels.
> - Rebase onto mm-new (2887e63d2e8e).
> - v1: https://lore.kernel.org/all/20260427142036.111940-2-shivankg@amd.com
>
> Test setup: AMD EPYC Zen 6, single socket, NPS2 (nodes 0 and 1 are
> DRAM, node 2 is CXL), with and without this patch.
>
> move_pages() throughput
> -----------------------
> A single move_pages() call migrates 1GB of anonymous memory, as 512
> 2MB THPs or as four 1GB hugetlb pages. Mean ± stddev (10 runs each),
> in GiB/s:
>
> 2MB THP:
> direction base patched speedup
> DRAM0 -> DRAM1 19.57 ± 0.17 27.51 ± 0.78 1.41x
> DRAM1 -> DRAM0 19.44 ± 0.12 27.33 ± 0.27 1.41x
> DRAM0 -> CXL 18.66 ± 0.18 26.90 ± 0.51 1.44x
> CXL -> DRAM0 15.38 ± 0.59 16.60 ± 0.24 1.08x
>
> 1GB hugetlb:
> direction base patched speedup
> DRAM0 -> DRAM1 20.34 ± 0.13 53.88 ± 1.48 2.65x
> DRAM1 -> DRAM0 19.10 ± 2.65 53.78 ± 0.55 2.82x
> DRAM0 -> CXL 19.53 ± 0.08 44.10 ± 0.19 2.26x
> CXL -> DRAM0 16.41 ± 0.07 20.89 ± 0.06 1.27x
>
> kswapd demotion
> ---------------
> Node 0 is limited to 16GB of free memory and demotion is enabled. A
> single thread bound to node 0 writes 96GB of anonymous THP memory, so
> ~80GB has to be demoted to CXL. Median of 10 runs:
>
> base patched
> kswapd CPU time 4.25 s 3.44 s (-19%)
> kswapd CPU per GB demoted 63.6 ms 44.1 ms (-31%)
> demoted by direct reclaim 14.1 GB 0.46 GB
> allocstall 7427 370
>
> With the patch, kswapd keeps up with the writer, so the writer rarely
> falls back to direct reclaim.
>
> Other workloads
> ---------------
> - Memory tiering (numa_balancing=2) with Redis, GAPBS PR/BC, a B-tree
> benchmark and Graph500: copying 2MB folios during demotion is
> 1.18-1.49x faster.
> - A userspace daemon moving 1GB hugetlb pages between DRAM and CXL
> with move_pages() while GUPS runs on them: demotion is 1.43x faster
> and promotion 1.28x faster.
> ---
> include/linux/highmem.h | 59 +++++++++++++++++++++++++++++++++++++++++++++++++
> mm/util.c | 18 +++++++++++----
> 2 files changed, 73 insertions(+), 4 deletions(-)
>
> diff --git a/include/linux/highmem.h b/include/linux/highmem.h
> index d7aac9de1c8a..e43459dc58b0 100644
> --- a/include/linux/highmem.h
> +++ b/include/linux/highmem.h
> @@ -484,6 +484,65 @@ static inline int copy_mc_highpage(struct page *to, struct page *from)
> }
> #endif
>
> +#if defined(copy_mc_to_kernel) && !defined(CONFIG_HIGHMEM) && \
> + !defined(__HAVE_ARCH_COPY_HIGHPAGE)
> +/*
> + * copy_mc_highpages() copies the whole range with a single
> + * copy_mc_to_kernel() call, which cannot reschedule. That is fine under
> + * preemptible models, but under cooperatively scheduled models callers
> + * should copy at most COPY_MC_PAGES_NON_PREEMPT_BATCH pages at a time.
> + *
> + * With a value of 32MB and assuming a memory bandwidth of ~10GBps, this
> + * should result in a worst case preemption latency of around 3ms when
> + * copying pages.
> + */
> +#define COPY_MC_PAGES_NON_PREEMPT_BATCH (SZ_32M >> PAGE_SHIFT)
> +
> +/**
> + * copy_mc_highpages() - copy a page range with machine check handling
> + * @to: first destination page
> + * @from: first source page
> + * @nr_pages: number of pages
> + *
> + * Range variant of copy_mc_highpage(). On a machine check, the poisoned
> + * source page is queued for memory_failure().
> + *
> + * Does not call cond_resched(). Under cooperatively scheduled models the
> + * caller is expected to limit @nr_pages to COPY_MC_PAGES_NON_PREEMPT_BATCH.
> + *
> + * Return: 0 on success, -EHWPOISON if a machine check was handled.
> + */
> +static inline int copy_mc_highpages(struct page *to, struct page *from,
> + unsigned long nr_pages)
> +{
> + unsigned long len = nr_pages << PAGE_SHIFT;
> + unsigned long ret, i;
> +
> + ret = copy_mc_to_kernel(page_address(to), page_address(from), len);
> + if (!ret) {
> + for (i = 0; i < nr_pages; i++)
> + kmsan_copy_page_meta(to + i, from + i);
> + return 0;
> + }
> + memory_failure_queue(page_to_pfn(from) + ((len - ret) >> PAGE_SHIFT), 0);
> + return -EHWPOISON;
> +}
> +#else
> +
> +#define COPY_MC_PAGES_NON_PREEMPT_BATCH 1
> +
> +static inline int copy_mc_highpages(struct page *to, struct page *from,
> + unsigned long nr_pages)
> +{
> + unsigned long i;
> +
> + for (i = 0; i < nr_pages; i++)
> + if (copy_mc_highpage(to + i, from + i))
> + return -EHWPOISON;
> + return 0;
> +}
> +#endif
> +
> static inline void memcpy_page(struct page *dst_page, size_t dst_off,
> struct page *src_page, size_t src_off,
> size_t len)
> diff --git a/mm/util.c b/mm/util.c
> index 7f374bb8bf36..5015cfdfeaa5 100644
> --- a/mm/util.c
> +++ b/mm/util.c
> @@ -763,13 +763,23 @@ EXPORT_SYMBOL(folio_copy);
>
> int folio_mc_copy(struct folio *dst, struct folio *src)
> {
> - long nr = folio_nr_pages(src);
> - long i = 0;
> + const unsigned long nr_pages = folio_nr_pages(src);
> + /*
> + * Copy the largest extent possible to allow for architecture specific
> + * extent based optimizations. copy_mc_highpages() does not call
> + * cond_resched(), so limit the unit size when running under
> + * non-preemptible scheduling models.
> + */
> + const unsigned long unit = preempt_model_preemptible() ?
> + nr_pages : COPY_MC_PAGES_NON_PREEMPT_BATCH;
> + unsigned long i = 0, count;
>
> for (;;) {
> - if (copy_mc_highpage(folio_page(dst, i), folio_page(src, i)))
> + count = min(unit, nr_pages - i);
> + if (copy_mc_highpages(folio_page(dst, i), folio_page(src, i), count))
> return -EHWPOISON;
> - if (++i == nr)
> + i += count;
> + if (i == nr_pages)
> break;
> cond_resched();
> }
>
> ---
> base-commit: 2887e63d2e8e0cf55402612d8996ce955a2c3e55
> change-id: 20260714-batpagecopy-5a2b45910e5d
>
> Best regards,
next prev parent reply other threads:[~2026-10-19 12:19 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 10:06 Shivank Garg
2026-10-19 11:09 ` Pedro Falcato
2026-10-19 12:23 ` David Hildenbrand (Arm)
2026-10-19 13:10 ` Garg, Shivank
2026-10-19 14:21 ` David Hildenbrand (Arm)
2026-10-19 17:32 ` Garg, Shivank
2026-10-19 13:16 ` Garg, Shivank
2026-10-19 12:19 ` David Laight [this message]
2026-10-19 18:54 ` Garg, Shivank
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261019131901.7a8fae90@pumpkin \
--to=david.laight.linux@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=ankur.a.arora@oracle.com \
--cc=bharata@amd.com \
--cc=bp@alien8.de \
--cc=dave.hansen@linux.intel.com \
--cc=david@kernel.org \
--cc=hpa@zytor.com \
--cc=hsalunke@amd.com \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=mingo@redhat.com \
--cc=nhuhuan@google.com \
--cc=rientjes@google.com \
--cc=rppt@kernel.org \
--cc=shivankg@amd.com \
--cc=surenb@google.com \
--cc=tejavojjala@google.com \
--cc=tglx@kernel.org \
--cc=torvalds@linuxfoundation.org \
--cc=vbabka@kernel.org \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®