Re: [PATCH 1/2] mm/shrinker: size shrinker_info_unit to a power of two
From: Qi Zheng
Date: Mon Oct 12 2026 - 22:52:47 EST
On 10/13/26 3:02 AM, Konstantin Khorenko wrote:
Every memory cgroup allocates, per NUMA node, one shrinker_info_unit
for every SHRINKER_UNIT_BITS shrinker ids up to shrinker_nr_max. The
unit holds BITS_PER_LONG atomic_long_t of deferred work, a power of
two, plus the one-word bitmap of shrinkers with objects charged to the
cgroup. That word takes the unit just past the power of two, to 520
bytes on 64-bit, so every unit comes from kmalloc-1k and almost half of
it is padding.
The units are allocated when the cgroup goes online and freed when it is
freed, so a dying cgroup keeps them until the last reference to it is
gone. Their number is the number of memory cgroups, times the NUMA
nodes, times shrinker_nr_max / SHRINKER_UNIT_BITS. shrinker_nr_max is
the peak number of memcg-aware shrinkers registered at once, and every
superblock registers one, so a host that mounts many filesystems and
runs many cgroups carries the padding as unreclaimable slab, and keeps
paying it for new cgroups after the mounts are gone.
Let a unit cover one id less than a word holds. The bitmap still fits
one word, and nr_deferred[] plus that word add up to BITS_PER_LONG
longs, so the unit fills a power-of-two kmalloc slot exactly and, on
64-bit, the memory taken by units halves, for about one unit more in
BITS_PER_LONG.
round_up() in expand_shrinker_info() needs a power-of-two step and
becomes roundup(); the other uses of SHRINKER_UNIT_BITS are divisions,
modulos and multiplications that do not care. Assert the unit size at
build time, since the layout depends on it.
Assisted-by: LLM
Signed-off-by: Konstantin Khorenko <khorenko@xxxxxxxxxxxxx>
---
include/linux/shrinker.h | 9 ++++++++-
mm/shrinker.c | 2 +-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h
index 1a00be90d93a9..c250ab68d346c 100644
--- a/include/linux/shrinker.h
+++ b/include/linux/shrinker.h
@@ -7,7 +7,11 @@
#include <linux/refcount.h>
#include <linux/completion.h>
-#define SHRINKER_UNIT_BITS BITS_PER_LONG
+/*
+ * One id less than a word holds, so that nr_deferred[] and the map word add
+ * up to BITS_PER_LONG longs: a power of two, which fills a kmalloc slot.
+ */
+#define SHRINKER_UNIT_BITS (BITS_PER_LONG - 1)
/*
* Bitmap and deferred work of shrinker::id corresponding to memcg-aware
@@ -18,6 +22,9 @@ struct shrinker_info_unit {
DECLARE_BITMAP(map, SHRINKER_UNIT_BITS);
};
+static_assert(!(sizeof(struct shrinker_info_unit) &
+ (sizeof(struct shrinker_info_unit) - 1)));
+
struct shrinker_info {
struct rcu_head rcu;
int map_nr_max;
diff --git a/mm/shrinker.c b/mm/shrinker.c
index 7ec2a9704f6f2..a9418072a509e 100644
--- a/mm/shrinker.c
+++ b/mm/shrinker.c
@@ -159,7 +159,7 @@ static int expand_one_shrinker_info(struct mem_cgroup *memcg, int new_size,
static int expand_shrinker_info(int new_id)
{
int ret = 0;
- int new_nr_max = round_up(new_id + 1, SHRINKER_UNIT_BITS);
+ int new_nr_max = roundup(new_id + 1, SHRINKER_UNIT_BITS);
int new_size, old_size = 0;
struct mem_cgroup *memcg;
Acked-by: Qi Zheng <qi.zheng@xxxxxxxxx>
Thanks,
Qi