]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
slab: skip percpu sheaves for remote object freeing
authorVlastimil Babka <vbabka@suse.cz>
Wed, 3 Sep 2025 12:59:49 +0000 (14:59 +0200)
committerVlastimil Babka <vbabka@suse.cz>
Mon, 29 Sep 2025 07:21:16 +0000 (09:21 +0200)
Since we don't control the NUMA locality of objects in percpu sheaves,
allocations with node restrictions bypass them. Allocations without
restrictions may however still expect to get local objects with high
probability, and the introduction of sheaves can decrease it due to
freed object from a remote node ending up in percpu sheaves.

The fraction of such remote frees seems low (5% on an 8-node machine)
but it can be expected that some cache or workload specific corner cases
exist. We can either conclude that this is not a problem due to the low
fraction, or we can make remote frees bypass percpu sheaves and go
directly to their slabs. This will make the remote frees more expensive,
but if it's only a small fraction, most frees will still benefit from
the lower overhead of percpu sheaves.

This patch thus makes remote object freeing bypass percpu sheaves,
including bulk freeing, and kfree_rcu() via the rcu_free sheaf. However
it's not intended to be 100% guarantee that percpu sheaves will only
contain local objects. The refill from slabs does not provide that
guarantee in the first place, and there might be cpu migrations
happening when we need to unlock the local_lock. Avoiding all that could
be possible but complicated so we can leave it for later investigation
whether it would be worth it. It can be expected that the more selective
freeing will itself prevent accumulation of remote objects in percpu
sheaves so any such violations would have only short-term effects.

Reviewed-by: Harry Yoo <harry.yoo@oracle.com>
Reviewed-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Vlastimil Babka <vbabka@suse.cz>
mm/slab_common.c
mm/slub.c

index 005a4319c06a01d2b616a75396fcc43766a62ddb..b6601e0fe598e24bd8d456dce4fc82c65b342bfd 100644 (file)
@@ -1623,8 +1623,11 @@ static bool kfree_rcu_sheaf(void *obj)
 
        slab = folio_slab(folio);
        s = slab->slab_cache;
-       if (s->cpu_sheaves)
-               return __kfree_rcu_sheaf(s, obj);
+       if (s->cpu_sheaves) {
+               if (likely(!IS_ENABLED(CONFIG_NUMA) ||
+                          slab_nid(slab) == numa_mem_id()))
+                       return __kfree_rcu_sheaf(s, obj);
+       }
 
        return false;
 }
index 596f375870af803d01bdd7861a4c17e05d7bb344..6bcddc513963fe59ba1c219a70c0487f0cd701b1 100644 (file)
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -472,6 +472,7 @@ struct slab_sheaf {
        };
        struct kmem_cache *cache;
        unsigned int size;
+       int node; /* only used for rcu_sheaf */
        void *objects[];
 };
 
@@ -5822,7 +5823,7 @@ static void rcu_free_sheaf(struct rcu_head *head)
         */
        __rcu_free_sheaf_prepare(s, sheaf);
 
-       barn = get_node(s, numa_mem_id())->barn;
+       barn = get_node(s, sheaf->node)->barn;
 
        /* due to slab_free_hook() */
        if (unlikely(sheaf->size == 0))
@@ -5912,10 +5913,12 @@ do_free:
         */
        rcu_sheaf->objects[rcu_sheaf->size++] = obj;
 
-       if (likely(rcu_sheaf->size < s->sheaf_capacity))
+       if (likely(rcu_sheaf->size < s->sheaf_capacity)) {
                rcu_sheaf = NULL;
-       else
+       } else {
                pcs->rcu_free = NULL;
+               rcu_sheaf->node = numa_mem_id();
+       }
 
        /*
         * we flush before local_unlock to make sure a racing
@@ -5946,7 +5949,11 @@ static void free_to_pcs_bulk(struct kmem_cache *s, size_t size, void **p)
        bool init = slab_want_init_on_free(s);
        unsigned int batch, i = 0;
        struct node_barn *barn;
+       void *remote_objects[PCS_BATCH_MAX];
+       unsigned int remote_nr = 0;
+       int node = numa_mem_id();
 
+next_remote_batch:
        while (i < size) {
                struct slab *slab = virt_to_slab(p[i]);
 
@@ -5956,7 +5963,15 @@ static void free_to_pcs_bulk(struct kmem_cache *s, size_t size, void **p)
                if (unlikely(!slab_free_hook(s, p[i], init, false))) {
                        p[i] = p[--size];
                        if (!size)
-                               return;
+                               goto flush_remote;
+                       continue;
+               }
+
+               if (unlikely(IS_ENABLED(CONFIG_NUMA) && slab_nid(slab) != node)) {
+                       remote_objects[remote_nr] = p[i];
+                       p[i] = p[--size];
+                       if (++remote_nr >= PCS_BATCH_MAX)
+                               goto flush_remote;
                        continue;
                }
 
@@ -6026,6 +6041,15 @@ no_empty:
         */
 fallback:
        __kmem_cache_free_bulk(s, size, p);
+
+flush_remote:
+       if (remote_nr) {
+               __kmem_cache_free_bulk(s, remote_nr, &remote_objects[0]);
+               if (i < size) {
+                       remote_nr = 0;
+                       goto next_remote_batch;
+               }
+       }
 }
 
 #ifndef CONFIG_SLUB_TINY
@@ -6117,8 +6141,13 @@ void slab_free(struct kmem_cache *s, struct slab *slab, void *object,
        if (unlikely(!slab_free_hook(s, object, slab_want_init_on_free(s), false)))
                return;
 
-       if (!s->cpu_sheaves || !free_to_pcs(s, object))
-               do_slab_free(s, slab, object, object, 1, addr);
+       if (s->cpu_sheaves && likely(!IS_ENABLED(CONFIG_NUMA) ||
+                                    slab_nid(slab) == numa_mem_id())) {
+               if (likely(free_to_pcs(s, object)))
+                       return;
+       }
+
+       do_slab_free(s, slab, object, object, 1, addr);
 }
 
 #ifdef CONFIG_MEMCG