diff mbox series

[4/8] mm: page_alloc: fix up block types when merging compatible blocks

Message ID 20230821183733.106619-5-hannes@cmpxchg.org (mailing list archive)
State New
Headers show
Series mm: page_alloc: freelist migratetype hygiene | expand

Commit Message

Johannes Weiner Aug. 21, 2023, 6:33 p.m. UTC
The buddy allocator coalesces compatible blocks during freeing, but it
doesn't update the types of the subblocks to match. When an allocation
later breaks the chunk down again, its pieces will be put on freelists
of the wrong type. This encourages incompatible page mixing (ask for
one type, get another), and thus long-term fragmentation.

Update the subblocks when merging a larger chunk, such that a later
expand() will maintain freelist type hygiene.

Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
---
 mm/page_alloc.c | 37 ++++++++++++++++++++++---------------
 1 file changed, 22 insertions(+), 15 deletions(-)

Comments

Zi Yan Aug. 21, 2023, 8:41 p.m. UTC | #1
On 21 Aug 2023, at 14:33, Johannes Weiner wrote:

> The buddy allocator coalesces compatible blocks during freeing, but it
> doesn't update the types of the subblocks to match. When an allocation
> later breaks the chunk down again, its pieces will be put on freelists
> of the wrong type. This encourages incompatible page mixing (ask for
> one type, get another), and thus long-term fragmentation.
>
> Update the subblocks when merging a larger chunk, such that a later
> expand() will maintain freelist type hygiene.
>
> Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
> ---
>  mm/page_alloc.c | 37 ++++++++++++++++++++++---------------
>  1 file changed, 22 insertions(+), 15 deletions(-)
>
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index a5e36d186893..6c9f565b2613 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -438,6 +438,17 @@ void set_pageblock_migratetype(struct page *page, int migratetype)
>  				page_to_pfn(page), MIGRATETYPE_MASK);
>  }
>
> +static void change_pageblock_range(struct page *pageblock_page,
> +					int start_order, int migratetype)
> +{
> +	int nr_pageblocks = 1 << (start_order - pageblock_order);
> +
> +	while (nr_pageblocks--) {
> +		set_pageblock_migratetype(pageblock_page, migratetype);
> +		pageblock_page += pageblock_nr_pages;
> +	}
> +}
> +

Is this code move included by accident?

>  #ifdef CONFIG_DEBUG_VM
>  static int page_outside_zone_boundaries(struct zone *zone, struct page *page)
>  {
> @@ -808,10 +819,17 @@ static inline void __free_one_page(struct page *page,
>  			 */
>  			int buddy_mt = get_pfnblock_migratetype(buddy, buddy_pfn);
>
> -			if (migratetype != buddy_mt
> -					&& (!migratetype_is_mergeable(migratetype) ||
> -						!migratetype_is_mergeable(buddy_mt)))
> -				goto done_merging;
> +			if (migratetype != buddy_mt) {
> +				if (!migratetype_is_mergeable(migratetype) ||
> +				    !migratetype_is_mergeable(buddy_mt))
> +					goto done_merging;
> +				/*
> +				 * Match buddy type. This ensures that
> +				 * an expand() down the line puts the
> +				 * sub-blocks on the right freelists.
> +				 */
> +				set_pageblock_migratetype(buddy, migratetype);
> +			}
>  		}
>
>  		/*
> @@ -1687,17 +1705,6 @@ int move_freepages_block(struct zone *zone, struct page *page,
>  								num_movable);
>  }
>
> -static void change_pageblock_range(struct page *pageblock_page,
> -					int start_order, int migratetype)
> -{
> -	int nr_pageblocks = 1 << (start_order - pageblock_order);
> -
> -	while (nr_pageblocks--) {
> -		set_pageblock_migratetype(pageblock_page, migratetype);
> -		pageblock_page += pageblock_nr_pages;
> -	}
> -}
> -
>  /*
>   * When we are falling back to another migratetype during allocation, try to
>   * steal extra free pages from the same pageblocks to satisfy further
> -- 
> 2.41.0


--
Best Regards,
Yan, Zi
Johannes Weiner Aug. 21, 2023, 9:20 p.m. UTC | #2
On Mon, Aug 21, 2023 at 04:41:44PM -0400, Zi Yan wrote:
> On 21 Aug 2023, at 14:33, Johannes Weiner wrote:
> 
> > The buddy allocator coalesces compatible blocks during freeing, but it
> > doesn't update the types of the subblocks to match. When an allocation
> > later breaks the chunk down again, its pieces will be put on freelists
> > of the wrong type. This encourages incompatible page mixing (ask for
> > one type, get another), and thus long-term fragmentation.
> >
> > Update the subblocks when merging a larger chunk, such that a later
> > expand() will maintain freelist type hygiene.
> >
> > Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
> > ---
> >  mm/page_alloc.c | 37 ++++++++++++++++++++++---------------
> >  1 file changed, 22 insertions(+), 15 deletions(-)
> >
> > diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> > index a5e36d186893..6c9f565b2613 100644
> > --- a/mm/page_alloc.c
> > +++ b/mm/page_alloc.c
> > @@ -438,6 +438,17 @@ void set_pageblock_migratetype(struct page *page, int migratetype)
> >  				page_to_pfn(page), MIGRATETYPE_MASK);
> >  }
> >
> > +static void change_pageblock_range(struct page *pageblock_page,
> > +					int start_order, int migratetype)
> > +{
> > +	int nr_pageblocks = 1 << (start_order - pageblock_order);
> > +
> > +	while (nr_pageblocks--) {
> > +		set_pageblock_migratetype(pageblock_page, migratetype);
> > +		pageblock_page += pageblock_nr_pages;
> > +	}
> > +}
> > +
> 
> Is this code move included by accident?

Ah, yes, my bad.

I used to call change_pageblock_range() at the end of the merge,
before adding the coalesced chunk to the freelist, for which I needed
this further up. Then I changed it to dealing with individual buddies
instead, and forgot to drop this part.

I'll remove it in the next version.
diff mbox series

Patch

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index a5e36d186893..6c9f565b2613 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -438,6 +438,17 @@  void set_pageblock_migratetype(struct page *page, int migratetype)
 				page_to_pfn(page), MIGRATETYPE_MASK);
 }
 
+static void change_pageblock_range(struct page *pageblock_page,
+					int start_order, int migratetype)
+{
+	int nr_pageblocks = 1 << (start_order - pageblock_order);
+
+	while (nr_pageblocks--) {
+		set_pageblock_migratetype(pageblock_page, migratetype);
+		pageblock_page += pageblock_nr_pages;
+	}
+}
+
 #ifdef CONFIG_DEBUG_VM
 static int page_outside_zone_boundaries(struct zone *zone, struct page *page)
 {
@@ -808,10 +819,17 @@  static inline void __free_one_page(struct page *page,
 			 */
 			int buddy_mt = get_pfnblock_migratetype(buddy, buddy_pfn);
 
-			if (migratetype != buddy_mt
-					&& (!migratetype_is_mergeable(migratetype) ||
-						!migratetype_is_mergeable(buddy_mt)))
-				goto done_merging;
+			if (migratetype != buddy_mt) {
+				if (!migratetype_is_mergeable(migratetype) ||
+				    !migratetype_is_mergeable(buddy_mt))
+					goto done_merging;
+				/*
+				 * Match buddy type. This ensures that
+				 * an expand() down the line puts the
+				 * sub-blocks on the right freelists.
+				 */
+				set_pageblock_migratetype(buddy, migratetype);
+			}
 		}
 
 		/*
@@ -1687,17 +1705,6 @@  int move_freepages_block(struct zone *zone, struct page *page,
 								num_movable);
 }
 
-static void change_pageblock_range(struct page *pageblock_page,
-					int start_order, int migratetype)
-{
-	int nr_pageblocks = 1 << (start_order - pageblock_order);
-
-	while (nr_pageblocks--) {
-		set_pageblock_migratetype(pageblock_page, migratetype);
-		pageblock_page += pageblock_nr_pages;
-	}
-}
-
 /*
  * When we are falling back to another migratetype during allocation, try to
  * steal extra free pages from the same pageblocks to satisfy further