Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: "David Hildenbrand (Arm)" <david@kernel.org>
To: Wei Yang <richard.weiyang@gmail.com>, Yuan Liu <yuan1.liu@intel.com>
Cc: Oscar Salvador <osalvador@suse.de>,
	Mike Rapoport <rppt@kernel.org>,
	linux-mm@kvack.org, Nanhai Zou <nanhai.zou@intel.com>,
	Chen Zhang <zhangchen.kidd@jd.com>,
	Jason Zeng <jason.zeng@intel.com>, Chen Yu <yu.c.chen@intel.com>,
	Pan Deng <pan.deng@intel.com>, Tianyou Li <tianyou.li@intel.com>,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH v8 2/2] mm/memory_hotplug: optimize zone contiguous check when changing pfn range
Date: Thu, 10 Sep 2026 17:39:58 +0200	[thread overview]
Message-ID: <a144c84d-bf82-49b5-b9ad-0f6938fd9b4b@kernel.org> (raw)
In-Reply-To: <20260904073856.5r56imgrwtpe56wl@master>

On 9/4/26 09:38, Wei Yang wrote:
> On Tue, Sep 01, 2026 at 01:29:50AM -0400, Yuan Liu wrote:
>> When move_pfn_range_to_zone() or remove_pfn_range_from_zone() updates a
>> zone, set_zone_contiguous() rescans the entire zone pageblock-by-pageblock
>> to rebuild zone->contiguous. For large zones this is a significant cost
>> during memory hotplug and hot-unplug.
>>
>> Add a new zone member, pages_with_online_memmap, that tracks the
>> number of pages within the zone span that have an online memory map,
>> including present pages and memory holes whose memory map has been
>> initialized and for which pfn_to_online_page() succeeds.
>>
>> For early boot memory, pages_with_online_memmap is calculated in
>> memmap_init_zone_range(). PFNs initialized by memmap_init_range() are
>> included in pages_with_online_memmap, and hole PFNs for which
>> pfn_to_online_page() succeeds are also counted in
>> init_unavailable_range(). For hotplugged memory,
>> pages_with_online_memmap is updated through adjust_present_page_count(),
>> which is called during memory online and offline operations. When
>> spanned_pages == pages_with_online_memmap, every PFN in the zone span
>> has a valid memmap entry, so pfn_to_page() can be called for any PFN
>> within the zone span without an additional pfn_valid() check.
>>
>> The counter may temporarily undercount when pages with an online
>> memory map exist outside the current zone span. This can only happen
>> during boot, when initializing the memory map of pages that do not
>> fall into any zone span. Growing the zone to cover such pages and
>> later shrinking it back may result in a value that is too small.
>> This is safe, as it merely prevents detecting a contiguous zone.
>>
>> The contiguity check using pages_with_online_memmap is stricter than
>> the old pageblock-by-pageblock scan. The old set_zone_contiguous()
>> iterated at pageblock granularity via pageblock_pfn_to_page(), so a
>> zone could be marked contiguous even if a subsection-sized hole
>> existed within a pageblock. The new check requires
>> spanned_pages == pages_with_online_memmap, meaning every PFN in the
>> zone span must satisfy pfn_to_online_page().
>>
>> The following test cases of memory hotplug for a VM [1], tested in the
>> environment [2], show that this optimization can significantly reduce the
>> memory hotplug time [3].
>>
>> +----------------+------+---------------+--------------+----------------+
>> |                | Size | Time (before) | Time (after) | Time Reduction |
>> |                +------+---------------+--------------+----------------+
>> | Plug Memory    | 256G |      10s      |      3s      |       70%      |
>> |                +------+---------------+--------------+----------------+
>> |                | 512G |      36s      |      7s      |       81%      |
>> +----------------+------+---------------+--------------+----------------+
>>
>> +----------------+------+---------------+--------------+----------------+
>> |                | Size | Time (before) | Time (after) | Time Reduction |
>> |                +------+---------------+--------------+----------------+
>> | Unplug Memory  | 256G |      11s      |      4s      |       64%      |
>> |                +------+---------------+--------------+----------------+
>> |                | 512G |      36s      |      9s      |       75%      |
>> +----------------+------+---------------+--------------+----------------+
>>
>> [1] Qemu commands to hotplug 256G/512G memory for a VM:
>>    object_add memory-backend-ram,id=hotmem0,size=256G/512G,share=on
>>    device_add virtio-mem-pci,id=vmem1,memdev=hotmem0,bus=port1
>>    qom-set vmem1 requested-size 256G/512G (Plug Memory)
>>    qom-set vmem1 requested-size 0G (Unplug Memory)
>>
>> [2] Hardware     : Intel Icelake server
>>    Guest Kernel : v7.3-rc1
>>    Qemu         : v9.0.0
>>
>>    Launch VM    :
>>    qemu-system-x86_64 -accel kvm -cpu host \
>>    -drive file=./Centos10_cloud.qcow2,format=qcow2,if=virtio \
>>    -drive file=./seed.img,format=raw,if=virtio \
>>    -smp 3,cores=3,threads=1,sockets=1,maxcpus=3 \
>>    -m 2G,slots=10,maxmem=2052472M \
>>    -device pcie-root-port,id=port1,bus=pcie.0,slot=1,multifunction=on \
>>    -device pcie-root-port,id=port2,bus=pcie.0,slot=2 \
>>    -nographic -machine q35 \
>>    -nic user,hostfwd=tcp::3000-:22
>>
>>    Guest kernel auto-onlines newly added memory blocks:
>>    echo online > /sys/devices/system/memory/auto_online_blocks
>>
>> [3] The time from typing the QEMU commands in [1] to when the output of
>>    'grep MemTotal /proc/meminfo' on Guest reflects that all hotplugged
>>    memory is recognized.
>>
>> Reported-by: Nanhai Zou <nanhai.zou@intel.com>
>> Reported-by: Chen Zhang <zhangchen.kidd@jd.com>
>> Tested-by: Yuan Liu <yuan1.liu@intel.com>
>> Reviewed-by: Jason Zeng <jason.zeng@intel.com>
>> Reviewed-by: Chen Yu <yu.c.chen@intel.com>
>> Reviewed-by: Pan Deng <pan.deng@intel.com>
>> Co-developed-by: Tianyou Li <tianyou.li@intel.com>
>> Signed-off-by: Tianyou Li <tianyou.li@intel.com>
>> Signed-off-by: Yuan Liu <yuan1.liu@intel.com>
>> ---
>> Documentation/mm/physical_memory.rst |  6 +++
>> drivers/base/memory.c                |  7 +++-
>> include/linux/mmzone.h               | 48 +++++++++++++++++++++
>> mm/memory_hotplug.c                  | 12 +-----
>> mm/mm_init.c                         | 63 +++++++++++++++-------------
>> mm/mm_init.h                         |  6 ---
>> mm/page_alloc.h                      |  2 +-
>> 7 files changed, 98 insertions(+), 46 deletions(-)
>>
>> diff --git a/Documentation/mm/physical_memory.rst b/Documentation/mm/physical_memory.rst
>> index a09407d72973..2e67e8b23a99 100644
>> --- a/Documentation/mm/physical_memory.rst
>> +++ b/Documentation/mm/physical_memory.rst
>> @@ -480,6 +480,12 @@ General
>>   ``present_pages`` should use ``get_online_mems()`` to get a stable value. It
>>   is initialized by ``calculate_node_totalpages()``.
>>
>> +``pages_with_online_memmap``
>> +  Pages within the zone that have an online memory map: present pages and
>> +  memory holes whose memory map has been initialized and
>> +  ``pfn_to_online_page()`` succeeds. See the comment for
>> +  ``pages_with_online_memmap`` in ``include/linux/mmzone.h`` for more details.
>> +
>> ``present_early_pages``
>>   The present pages existing within the zone located on memory available since
>>   early boot, excluding hotplugged memory. Defined only when
>> diff --git a/drivers/base/memory.c b/drivers/base/memory.c
>> index 5eead3346f1e..28f9503f6a71 100644
>> --- a/drivers/base/memory.c
>> +++ b/drivers/base/memory.c
>> @@ -255,6 +255,7 @@ static int memory_block_online(struct memory_block *mem)
>> 		nr_vmemmap_pages = mem->altmap->free;
>>
>> 	mem_hotplug_begin();
>> +	clear_zone_contiguous(zone);
>> 	if (nr_vmemmap_pages) {
>> 		ret = mhp_init_memmap_on_memory(start_pfn, nr_vmemmap_pages, zone);
>> 		if (ret)
>> @@ -279,6 +280,7 @@ static int memory_block_online(struct memory_block *mem)
>>
>> 	mem->zone = zone;
>> out:
>> +	set_zone_contiguous(zone);
>> 	mem_hotplug_done();
>> 	return ret;
>> }
>> @@ -304,6 +306,7 @@ static int memory_block_offline(struct memory_block *mem)
>> 		nr_vmemmap_pages = mem->altmap->free;
>>
>> 	mem_hotplug_begin();
>> +	clear_zone_contiguous(mem->zone);
>> 	if (nr_vmemmap_pages)
>> 		adjust_present_page_count(pfn_to_page(start_pfn), mem->group,
>> 					  -nr_vmemmap_pages);
>> @@ -321,8 +324,10 @@ static int memory_block_offline(struct memory_block *mem)
>> 	if (nr_vmemmap_pages)
>> 		mhp_deinit_memmap_on_memory(start_pfn, nr_vmemmap_pages);
>>
>> -	mem->zone = NULL;
>> out:
>> +	set_zone_contiguous(mem->zone);
>> +	if (!ret)
>> +		mem->zone = NULL;
>> 	mem_hotplug_done();
>> 	return ret;
>> }
>> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
>> index 94f9c3ff5416..7bfb871d6344 100644
>> --- a/include/linux/mmzone.h
>> +++ b/include/linux/mmzone.h
>> @@ -1043,6 +1043,21 @@ struct zone {
>> 	 * cma pages is present pages that are assigned for CMA use
>> 	 * (MIGRATE_CMA).
>> 	 *
>> +	 * pages_with_online_memmap tracks pages within the zone that have
>> +	 * an online memory map: present pages and memory holes whose
>> +	 * memory map has been initialized and pfn_to_online_page()
>> +	 * succeeds. When spanned_pages == pages_with_online_memmap,
>> +	 * pfn_to_page() can be performed without further checks on any
>> +	 * PFN within the zone span.
>> +	 *
>> +	 * Note: this counter may temporarily undercount when pages with an
>> +	 * online memory map exist outside the current zone span. This can
>> +	 * only happen during boot, when initializing the memory map of
>> +	 * pages that do not fall into any zone span. Growing the zone to
>> +	 * cover such pages and later shrinking it back may result in a
>> +	 * "too small" value. This is safe: it merely prevents detecting a
>> +	 * contiguous zone.
>> +	 *
>> 	 * So present_pages may be used by memory hotplug or memory power
>> 	 * management logic to figure out unmanaged pages by checking
>> 	 * (present_pages - managed_pages). And managed_pages should be used
>> @@ -1067,6 +1082,7 @@ struct zone {
>> 	atomic_long_t		managed_pages;
>> 	unsigned long		spanned_pages;
>> 	unsigned long		present_pages;
>> +	unsigned long		pages_with_online_memmap;
>> #if defined(CONFIG_MEMORY_HOTPLUG)
>> 	unsigned long		present_early_pages;
>> #endif
>> @@ -1694,6 +1710,38 @@ static inline bool zone_is_zone_device(const struct zone *zone)
>> }
>> #endif
>>
>> +/**
>> + * zone_is_contiguous - test whether a zone is contiguous
>> + * @zone: the zone to test.
>> + *
>> + * In a contiguous zone, it is valid to call pfn_to_page() on any PFN in the
>> + * spanned zone without requiring pfn_valid() or pfn_to_online_page() checks.
>> + *
>> + * Note that missing synchronization with memory offlining makes any PFN
>> + * traversal prone to races.
>> + *
>> + * ZONE_DEVICE zones are always marked non-contiguous.
>> + *
>> + * Return: true if contiguous, otherwise false.
>> + */
>> +static inline bool zone_is_contiguous(const struct zone *zone)
>> +{
>> +	return READ_ONCE(zone->contiguous);
>> +}
>> +
>> +static inline void set_zone_contiguous(struct zone *zone)
>> +{
>> +	if (zone_is_zone_device(zone))
>> +		return;
> 
> After this patch, set_zone_contiguous() is only used in two cases:
> 
>   * memory_block_online()
>   * page_alloc_init_late()
> 
> If I understand correctly:
> 
>   * zone_for_pfn_range() won't return ZONE_DEVICE
>   * there is no ZONE_DEVICE memory populated at this point, device memory is
>     populated during do_initcalls()
> 
> So we don't expect ZONE_DEVICE here?
> 
>> +	if (zone->spanned_pages == zone->pages_with_online_memmap)
>> +		WRITE_ONCE(zone->contiguous, true);
>> +}
>> +
>> +static inline void clear_zone_contiguous(struct zone *zone)
>> +{
>> +	WRITE_ONCE(zone->contiguous, false);
>> +}
>> +
>> /*
>>  * Returns true if a zone has pages managed by the buddy allocator.
>>  * All the reclaim decisions have to use this function rather than
>> diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
>> index 9f19876ec3ec..100941d1b828 100644
>> --- a/mm/memory_hotplug.c
>> +++ b/mm/memory_hotplug.c
>> @@ -549,18 +549,13 @@ void remove_pfn_range_from_zone(struct zone *zone,
>>
>> 	/*
>> 	 * Zone shrinking code cannot properly deal with ZONE_DEVICE. So
>> -	 * we will not try to shrink the zones - which is okay as
>> -	 * set_zone_contiguous() cannot deal with ZONE_DEVICE either way.
>> +	 * we will not try to shrink it.
>> 	 */
>> 	if (zone_is_zone_device(zone))
>> 		return;
> 
> One question not closely related to this patch.
> 
> This check is introduced in commit 7ce700bf11b5 ("mm/memory_hotplug: don't
> access uninitialized memmaps in shrink_zone_span()"), at that time
> pfn_to_online_page() couldn't handle ZONE_DEVICE pfn correctly.
> 
> Then commit 1f90a3477df3 ("mm: teach pfn_to_online_page() about ZONE_DEVICE
> section collisions") enables it.

That's something different. pfn_to_online_page() will always fail on ZONE_DEVICE
parts as ZONE_DEVICE pages are never online.

We'd have to hand-code some check similar to what is done in
pfn_to_online_page() to deal with collisions in online_device_section(ms) and
provide a custom shrinking alternative.

But given that there is no actual demand (nobody uses zone->contig there), it
doesn't really make sense to add support.

-- 
Cheers,

David


  parent reply	other threads:[~2026-09-10 15:40 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-01  5:29 [PATCH v8 0/2] mm/memory_hotplug: optimize zone contiguous check when changing pfn range Yuan Liu
2026-09-01  5:29 ` [PATCH v8 1/2] mm/memory_hotplug: make shrink_zone_span() more robust Yuan Liu
2026-09-04  2:42   ` Wei Yang
2026-09-08  7:52     ` Liu, Yuan1
2026-09-01  5:29 ` [PATCH v8 2/2] mm/memory_hotplug: optimize zone contiguous check when changing pfn range Yuan Liu
2026-09-04  7:38   ` Wei Yang
2026-09-08  8:03     ` Liu, Yuan1
2026-09-10 15:39     ` David Hildenbrand (Arm) [this message]
2026-09-10 15:35   ` David Hildenbrand (Arm)
2026-09-11  6:37     ` Liu, Yuan1

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=a144c84d-bf82-49b5-b9ad-0f6938fd9b4b@kernel.org \
    --to=david@kernel.org \
    --cc=jason.zeng@intel.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=nanhai.zou@intel.com \
    --cc=osalvador@suse.de \
    --cc=pan.deng@intel.com \
    --cc=richard.weiyang@gmail.com \
    --cc=rppt@kernel.org \
    --cc=tianyou.li@intel.com \
    --cc=yu.c.chen@intel.com \
    --cc=yuan1.liu@intel.com \
    --cc=zhangchen.kidd@jd.com \
    /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