Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mm/slub: batch-detach node partial slabs
@ 2026-05-25  3:22 Hao Li
  2026-05-26  7:37 ` Harry Yoo
  0 siblings, 1 reply; 5+ messages in thread
From: Hao Li @ 2026-05-25  3:22 UTC (permalink / raw)
  To: vbabka, harry, akpm
  Cc: cl, rientjes, roman.gushchin, linux-mm, linux-kernel, Hao Li

get_partial_node_bulk() used to move each selected slab from the node
partial list to the local pc->slabs list using a remove_partial() and
list_add() pair. In practice, the loop often detaches several adjacent
slabs, so this repeatedly manipulates list pointers while holding
n->list_lock, which causes unnecessary churn.

Instead, track contiguous runs of matching slabs and move each run with
list_bulk_move_tail() in one operation. This reduces list pointer churn
inside the lock critical section.

The mmap2 testcase shows a 5% improvement after applying this patch.

Signed-off-by: Hao Li <hao.li@linux.dev>
---
 mm/slub.c | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/mm/slub.c b/mm/slub.c
index 04692a6f9128..180973a4a3d2 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -3739,6 +3739,7 @@ static bool get_partial_node_bulk(struct kmem_cache *s,
 				  bool allow_spin)
 {
 	struct slab *slab, *slab2;
+	struct slab *first = NULL, *last = NULL;
 	unsigned int total_free = 0;
 	unsigned long flags;
 
@@ -3757,8 +3758,15 @@ static bool get_partial_node_bulk(struct kmem_cache *s,
 		struct freelist_counters flc;
 		unsigned int slab_free;
 
-		if (!pfmemalloc_match(slab, pc->flags))
+		if (!pfmemalloc_match(slab, pc->flags)) {
+			if (first) {
+				list_bulk_move_tail(&pc->slabs,
+						    &first->slab_list,
+						    &last->slab_list);
+				first = NULL;
+			}
 			continue;
+		}
 
 		/*
 		 * determine the number of free objects in the slab racily
@@ -3775,15 +3783,21 @@ static bool get_partial_node_bulk(struct kmem_cache *s,
 		    && total_free + slab_free > pc->max_objects)
 			break;
 
-		remove_partial(n, slab);
-
-		list_add(&slab->slab_list, &pc->slabs);
+		if (!first)
+			first = slab;
+		last = slab;
+		slab_clear_node_partial(slab);
+		n->nr_partial--;
 
 		total_free += slab_free;
 		if (total_free >= pc->max_objects)
 			break;
 	}
 
+	if (first)
+		list_bulk_move_tail(&pc->slabs, &first->slab_list,
+				    &last->slab_list);
+
 	spin_unlock_irqrestore(&n->list_lock, flags);
 	return total_free > 0;
 }
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH] mm/slub: batch-detach node partial slabs
  2026-05-25  3:22 [PATCH] mm/slub: batch-detach node partial slabs Hao Li
@ 2026-05-26  7:37 ` Harry Yoo
  2026-05-26  9:08   ` Hao Li
  2026-05-26 13:41   ` Vlastimil Babka (SUSE)
  0 siblings, 2 replies; 5+ messages in thread
From: Harry Yoo @ 2026-05-26  7:37 UTC (permalink / raw)
  To: Hao Li, vbabka, akpm; +Cc: cl, rientjes, roman.gushchin, linux-mm, linux-kernel


[-- Attachment #1.1: Type: text/plain, Size: 2086 bytes --]



On 5/25/26 12:22 PM, Hao Li wrote:
> get_partial_node_bulk() used to move each selected slab from the node
> partial list to the local pc->slabs list using a remove_partial() and
> list_add() pair. In practice, the loop often detaches several adjacent
> slabs, so this repeatedly manipulates list pointers while holding
> n->list_lock, which causes unnecessary churn.
> 
> Instead, track contiguous runs of matching slabs and move each run with
> list_bulk_move_tail() in one operation.

TIL list_bulk_move_tail() :D

 > This reduces list pointer churn> inside the lock critical section.

Similar to this, can we return all slabs in pc->slabs at once when 
returning those slabs to the list? ... I see Vlastimil removed 'nr of 
empty slabs' check in the other series already.

Now that it inserts slabs to the tail with Vlastimil's patchset, let's 
do a list_splice_tail() instead?

> The mmap2 testcase shows a 5% improvement after applying this patch.
> 
> Signed-off-by: Hao Li <hao.li@linux.dev>
> ---
>   mm/slub.c | 22 ++++++++++++++++++----
>   1 file changed, 18 insertions(+), 4 deletions(-)
> 
> diff --git a/mm/slub.c b/mm/slub.c
> index 04692a6f9128..180973a4a3d2 100644
> --- a/mm/slub.c
> +++ b/mm/slub.c
> @@ -3775,15 +3783,21 @@ static bool get_partial_node_bulk(struct kmem_cache *s,
>   		    && total_free + slab_free > pc->max_objects)
>   			break;
>   
> -		remove_partial(n, slab);
> -
> -		list_add(&slab->slab_list, &pc->slabs);
> +		if (!first)
> +			first = slab;
> +		last = slab;

> +		slab_clear_node_partial(slab);
> +		n->nr_partial--;

Perhaps factor out those two statements into to a common function and 
call it in get_partial_node_bulk() and remove_partial()?
>   		total_free += slab_free;
>   		if (total_free >= pc->max_objects)
>   			break;
>   	}
>   
> +	if (first)
> +		list_bulk_move_tail(&pc->slabs, &first->slab_list,
> +				    &last->slab_list);
> +
>   	spin_unlock_irqrestore(&n->list_lock, flags);
>   	return total_free > 0;
>   }

-- 
Cheers,
Harry / Hyeonggon


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 265 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] mm/slub: batch-detach node partial slabs
  2026-05-26  7:37 ` Harry Yoo
@ 2026-05-26  9:08   ` Hao Li
  2026-05-26 13:41   ` Vlastimil Babka (SUSE)
  1 sibling, 0 replies; 5+ messages in thread
From: Hao Li @ 2026-05-26  9:08 UTC (permalink / raw)
  To: Harry Yoo
  Cc: vbabka, akpm, cl, rientjes, roman.gushchin, linux-mm,
	linux-kernel

On Tue, May 26, 2026 at 04:37:46PM +0900, Harry Yoo wrote:
> 
> 
> On 5/25/26 12:22 PM, Hao Li wrote:
> > get_partial_node_bulk() used to move each selected slab from the node
> > partial list to the local pc->slabs list using a remove_partial() and
> > list_add() pair. In practice, the loop often detaches several adjacent
> > slabs, so this repeatedly manipulates list pointers while holding
> > n->list_lock, which causes unnecessary churn.
> > 
> > Instead, track contiguous runs of matching slabs and move each run with
> > list_bulk_move_tail() in one operation.
> 
> TIL list_bulk_move_tail() :D

I had to dig through list.h for ages just to find it :P

> 
> > This reduces list pointer churn> inside the lock critical section.
> 
> Similar to this, can we return all slabs in pc->slabs at once when returning
> those slabs to the list? ... I see Vlastimil removed 'nr of empty slabs'
> check in the other series already.
> 
> Now that it inserts slabs to the tail with Vlastimil's patchset, let's do a
> list_splice_tail() instead?

Great idea! then both get and put operations will be highly efficient.

> 
> > The mmap2 testcase shows a 5% improvement after applying this patch.
> > 
> > Signed-off-by: Hao Li <hao.li@linux.dev>
> > ---
> >   mm/slub.c | 22 ++++++++++++++++++----
> >   1 file changed, 18 insertions(+), 4 deletions(-)
> > 
> > diff --git a/mm/slub.c b/mm/slub.c
> > index 04692a6f9128..180973a4a3d2 100644
> > --- a/mm/slub.c
> > +++ b/mm/slub.c
> > @@ -3775,15 +3783,21 @@ static bool get_partial_node_bulk(struct kmem_cache *s,
> >   		    && total_free + slab_free > pc->max_objects)
> >   			break;
> > -		remove_partial(n, slab);
> > -
> > -		list_add(&slab->slab_list, &pc->slabs);
> > +		if (!first)
> > +			first = slab;
> > +		last = slab;
> 
> > +		slab_clear_node_partial(slab);
> > +		n->nr_partial--;
> 
> Perhaps factor out those two statements into to a common function and call
> it in get_partial_node_bulk() and remove_partial()?

Agreed, this is very reasonable.

> >   		total_free += slab_free;
> >   		if (total_free >= pc->max_objects)
> >   			break;
> >   	}
> > +	if (first)
> > +		list_bulk_move_tail(&pc->slabs, &first->slab_list,
> > +				    &last->slab_list);
> > +
> >   	spin_unlock_irqrestore(&n->list_lock, flags);
> >   	return total_free > 0;
> >   }
> 

Thanks for the review!

-- 
Thanks,
Hao


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] mm/slub: batch-detach node partial slabs
  2026-05-26  7:37 ` Harry Yoo
  2026-05-26  9:08   ` Hao Li
@ 2026-05-26 13:41   ` Vlastimil Babka (SUSE)
  2026-05-27  6:08     ` Hao Li
  1 sibling, 1 reply; 5+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-05-26 13:41 UTC (permalink / raw)
  To: Harry Yoo, Hao Li, akpm
  Cc: cl, rientjes, roman.gushchin, linux-mm, linux-kernel

On 5/26/26 9:37 AM, Harry Yoo wrote:
> 
> 
> On 5/25/26 12:22 PM, Hao Li wrote:
>> get_partial_node_bulk() used to move each selected slab from the node
>> partial list to the local pc->slabs list using a remove_partial() and
>> list_add() pair. In practice, the loop often detaches several adjacent
>> slabs, so this repeatedly manipulates list pointers while holding
>> n->list_lock, which causes unnecessary churn.
>>
>> Instead, track contiguous runs of matching slabs and move each run with
>> list_bulk_move_tail() in one operation.
> 
> TIL list_bulk_move_tail() :D
> 
>> This reduces list pointer churn> inside the lock critical section.

Nice!

> Similar to this, can we return all slabs in pc->slabs at once when
> returning those slabs to the list? ... I see Vlastimil removed 'nr of
> empty slabs' check in the other series already.
> 
> Now that it inserts slabs to the tail with Vlastimil's patchset, let's
> do a list_splice_tail() instead?

Why not, although it should be rare to return more than one slab in that
path. So in case this change gets tricky for some reason, we can leave it.

BTW, if you also get the same idea as I had and try to replace the
current "remove from pc.slabs initially, reinsert to pc.slabs if it was
a partial refill" with a "keep in pc.slabs and only remove if the refill
was full" to reduce pointer churn, don't try that, it crashes rather
quickly :D

>> The mmap2 testcase shows a 5% improvement after applying this patch.
>>
>> Signed-off-by: Hao Li <hao.li@linux.dev>
>> ---
>>   mm/slub.c | 22 ++++++++++++++++++----
>>   1 file changed, 18 insertions(+), 4 deletions(-)
>>
>> diff --git a/mm/slub.c b/mm/slub.c
>> index 04692a6f9128..180973a4a3d2 100644
>> --- a/mm/slub.c
>> +++ b/mm/slub.c
>> @@ -3775,15 +3783,21 @@ static bool get_partial_node_bulk(struct
>> kmem_cache *s,
>>               && total_free + slab_free > pc->max_objects)
>>               break;
>>   -        remove_partial(n, slab);
>> -
>> -        list_add(&slab->slab_list, &pc->slabs);
>> +        if (!first)
>> +            first = slab;
>> +        last = slab;
> 
>> +        slab_clear_node_partial(slab);
>> +        n->nr_partial--;
> 
> Perhaps factor out those two statements into to a common function and
> call it in get_partial_node_bulk() and remove_partial()?
>>           total_free += slab_free;
>>           if (total_free >= pc->max_objects)
>>               break;
>>       }
>>   +    if (first)
>> +        list_bulk_move_tail(&pc->slabs, &first->slab_list,
>> +                    &last->slab_list);
>> +
>>       spin_unlock_irqrestore(&n->list_lock, flags);
>>       return total_free > 0;
>>   }
> 



^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] mm/slub: batch-detach node partial slabs
  2026-05-26 13:41   ` Vlastimil Babka (SUSE)
@ 2026-05-27  6:08     ` Hao Li
  0 siblings, 0 replies; 5+ messages in thread
From: Hao Li @ 2026-05-27  6:08 UTC (permalink / raw)
  To: Vlastimil Babka (SUSE), Harry Yoo
  Cc: akpm, cl, rientjes, roman.gushchin, linux-mm, linux-kernel

On Tue, May 26, 2026 at 03:41:55PM +0200, Vlastimil Babka (SUSE) wrote:
> On 5/26/26 9:37 AM, Harry Yoo wrote:
> > 
> > 
> > On 5/25/26 12:22 PM, Hao Li wrote:
> >> get_partial_node_bulk() used to move each selected slab from the node
> >> partial list to the local pc->slabs list using a remove_partial() and
> >> list_add() pair. In practice, the loop often detaches several adjacent
> >> slabs, so this repeatedly manipulates list pointers while holding
> >> n->list_lock, which causes unnecessary churn.
> >>
> >> Instead, track contiguous runs of matching slabs and move each run with
> >> list_bulk_move_tail() in one operation.
> > 
> > TIL list_bulk_move_tail() :D
> > 
> >> This reduces list pointer churn> inside the lock critical section.
> 
> Nice!
> 
> > Similar to this, can we return all slabs in pc->slabs at once when
> > returning those slabs to the list? ... I see Vlastimil removed 'nr of
> > empty slabs' check in the other series already.
> > 
> > Now that it inserts slabs to the tail with Vlastimil's patchset, let's
> > do a list_splice_tail() instead?
> 
> Why not, although it should be rare to return more than one slab in that
> path.

Yeah, it's definitely still worth doing.

> So in case this change gets tricky for some reason, we can leave it.

sure, I will try and test.

> 
> BTW, if you also get the same idea as I had and try to replace the
> current "remove from pc.slabs initially, reinsert to pc.slabs if it was
> a partial refill" with a "keep in pc.slabs and only remove if the refill
> was full" to reduce pointer churn, don't try that, it crashes rather
> quickly :D

Ah, fair enough. But honestly, it did seem like a good idea initially.

> 
> >> The mmap2 testcase shows a 5% improvement after applying this patch.
> >>
> >> Signed-off-by: Hao Li <hao.li@linux.dev>
> >> ---
> >>   mm/slub.c | 22 ++++++++++++++++++----
> >>   1 file changed, 18 insertions(+), 4 deletions(-)
> >>
> >> diff --git a/mm/slub.c b/mm/slub.c
> >> index 04692a6f9128..180973a4a3d2 100644
> >> --- a/mm/slub.c
> >> +++ b/mm/slub.c
> >> @@ -3775,15 +3783,21 @@ static bool get_partial_node_bulk(struct
> >> kmem_cache *s,
> >>               && total_free + slab_free > pc->max_objects)
> >>               break;
> >>   -        remove_partial(n, slab);
> >> -
> >> -        list_add(&slab->slab_list, &pc->slabs);
> >> +        if (!first)
> >> +            first = slab;
> >> +        last = slab;
> > 
> >> +        slab_clear_node_partial(slab);
> >> +        n->nr_partial--;
> > 
> > Perhaps factor out those two statements into to a common function and
> > call it in get_partial_node_bulk() and remove_partial()?
> >>           total_free += slab_free;
> >>           if (total_free >= pc->max_objects)
> >>               break;
> >>       }
> >>   +    if (first)
> >> +        list_bulk_move_tail(&pc->slabs, &first->slab_list,
> >> +                    &last->slab_list);
> >> +
> >>       spin_unlock_irqrestore(&n->list_lock, flags);
> >>       return total_free > 0;
> >>   }
> > 
> 

-- 
Thanks,
Hao


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-05-27  6:09 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-25  3:22 [PATCH] mm/slub: batch-detach node partial slabs Hao Li
2026-05-26  7:37 ` Harry Yoo
2026-05-26  9:08   ` Hao Li
2026-05-26 13:41   ` Vlastimil Babka (SUSE)
2026-05-27  6:08     ` Hao Li

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox