Re: [PATCH 1/2] mm/shrinker: size shrinker_info_unit to a power of two

From: Muchun Song

Date: Tue Oct 13 2026 - 02:27:15 EST




> On Oct 13, 2026, at 03:02, Konstantin Khorenko <khorenko@xxxxxxxxxxxxx> 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)));

This assertion is not straightforward. I'd like to use is_power_of_two().
Like:

BUILD_BUG_ON(!is_power_of_2(sizeof(struct shrinker_info_unit)));

Muchun,
Thanks.

> +
> 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;
>
>
> --
> 2.43.0
>