Re: [RFC 1/2] mm: page_alloc: replace pageblock_flags bitmap with struct pageblock_data

From: Zi Yan

Date: Sun Apr 19 2026 - 21:40:37 EST


On 3 Apr 2026, at 15:40, Johannes Weiner wrote:

> From: Johannes Weiner <jweiner@xxxxxxxx>
>
> Replace the packed pageblock_flags bitmap with a per-pageblock struct
> containing its own flags word. This changes the storage from
> NR_PAGEBLOCK_BITS bits per pageblock packed into shared unsigned longs,
> to a dedicated unsigned long per pageblock.
>
> The free path looks up migratetype (from pageblock flags) immediately
> followed by looking up pageblock ownership. Colocating them in a struct
> means this hot path touches one cache line instead of two.
>
> The per-pageblock struct also eliminates all the bit-packing indexing
> (pfn_to_bitidx, word selection, intra-word shifts), simplifying the
> accessor code.
>
> Memory overhead: 8 bytes per pageblock (one unsigned long). With 2MB
> pageblocks on x86_64, that's 4KB per GB -- up from ~0.5-1 bytes per
> pageblock with the packed bitmap, but still negligible in absolute terms.
>
> No functional change.
>
> Signed-off-by: Johannes Weiner <hannes@xxxxxxxxxxx>
> ---
> include/linux/mmzone.h | 15 ++++----
> mm/internal.h | 17 +++++++++
> mm/mm_init.c | 25 ++++++-------
> mm/page_alloc.c | 81 ++++++------------------------------------
> mm/sparse.c | 3 +-
> 5 files changed, 48 insertions(+), 93 deletions(-)
>
> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
> index 3e51190a55e4..2f202bda5ec6 100644
> --- a/include/linux/mmzone.h
> +++ b/include/linux/mmzone.h
> @@ -916,7 +916,7 @@ struct zone {
> * Flags for a pageblock_nr_pages block. See pageblock-flags.h.
> * In SPARSEMEM, this map is stored in struct mem_section
> */
> - unsigned long *pageblock_flags;
> + struct pageblock_data *pageblock_data;
> #endif /* CONFIG_SPARSEMEM */
>
> /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
> @@ -1866,9 +1866,6 @@ static inline bool movable_only_nodes(nodemask_t *nodes)
> #define PAGES_PER_SECTION (1UL << PFN_SECTION_SHIFT)
> #define PAGE_SECTION_MASK (~(PAGES_PER_SECTION-1))
>
> -#define SECTION_BLOCKFLAGS_BITS \
> - ((1UL << (PFN_SECTION_SHIFT - pageblock_order)) * NR_PAGEBLOCK_BITS)
> -
> #if (MAX_PAGE_ORDER + PAGE_SHIFT) > SECTION_SIZE_BITS
> #error Allocator MAX_PAGE_ORDER exceeds SECTION_SIZE
> #endif
> @@ -1901,13 +1898,17 @@ static inline unsigned long section_nr_to_pfn(unsigned long sec)
> #define SUBSECTION_ALIGN_UP(pfn) ALIGN((pfn), PAGES_PER_SUBSECTION)
> #define SUBSECTION_ALIGN_DOWN(pfn) ((pfn) & PAGE_SUBSECTION_MASK)
>
> +struct pageblock_data {
> + unsigned long flags;

Would it be better to make this uint32_t if !CONFIG_MEMORY_ISOLATION
and uint64_t otherwise? MIGRATE_ISOLATE is the only reason to have
8 byte pageblock flag.

<snip>

> -#ifdef CONFIG_MEMORY_ISOLATION
> - BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 8);
> -#else
> - BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 4);
> -#endif

We probably still need

BUILD_BUG_ON(NR_PAGEBLOCK_BITS > sizeof(struct pageblock_data));

just in case in the future we add too many pageblock bits.


Otherwise, this patch can be sent and merged separately.


--
Best Regards,
Yan, Zi