All of 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.