From: Mykyta Yatsenko <mykyta.yatsenko5@gmail.com>
To: "T.J. Mercier" <tjmercier@google.com>,
ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org,
eddyz87@gmail.com, memxor@gmail.com, martin.lau@linux.dev,
song@kernel.org, yonghong.song@linux.dev, jolsa@kernel.org,
emil@etsalapatis.com
Cc: bpf@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH bpf-next v3 1/2] bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem
Date: Fri, 7 Aug 2026 16:25:52 +0100 [thread overview]
Message-ID: <e417cb09-8466-4d9e-9d33-7330d84df12a@gmail.com> (raw)
In-Reply-To: <20260805223516.1495988-2-tjmercier@google.com>
On 8/5/26 11:35 PM, T.J. Mercier wrote:
> The htab_elem struct is used as the per-element type for all BPF hash
> map types and includes bpf_lru_node in a union with a ptr_to_pptr
> pointer. For standard (non-LRU, non-PCPU) hash maps, the 24 byte union
> allocated for every element is entirely unused. For non-preallocated
> PCPU maps, ptr_to_pptr only requires 8 bytes, leaving 16 bytes of unused
> overhead in the union. For preallocated PCPU maps ptr_to_pptr is unused
> since elements are freed to the PCPU freelist.
>
> Eliminate this per-element memory overhead by splitting htab_elem into
> dedicated structures for each map type:
> - struct htab_elem: Minimal structure for standard hash maps and
> preallocated PCPU maps (saves 24 bytes per element).
> - struct htab_elem_pcpu: Structure for non-preallocated PCPU maps
> containing ptr_to_pptr (saves 16 bytes per element).
> - struct htab_elem_lru: Retains struct bpf_lru_node for LRU maps.
>
> Because element sizes now vary by map type, add key_offset to struct
> bpf_htab to track the dynamic key offset. Update helper accessors and
> lookups to compute key and value offsets using htab->key_offset.
>
> Pointers to struct htab_elem in the existing code (e.g. htab_elem_hash)
> serve as generic base element pointers. This is possible because
> htab_elem, htab_elem_pcpu, and htab_elem_lru share a common initial
> sequence, making pointer casts safe.
>
> Signed-off-by: T.J. Mercier <tjmercier@google.com>
> ---
> kernel/bpf/hashtab.c | 362 +++++++++++++++++++++++++---------------
> kernel/bpf/map_in_map.c | 13 ++
> kernel/bpf/map_in_map.h | 2 +
> 3 files changed, 242 insertions(+), 135 deletions(-)
>
> diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c
> index 9f394e1aa2e8..f54366da459f 100644
> --- a/kernel/bpf/hashtab.c
> +++ b/kernel/bpf/hashtab.c
> @@ -102,11 +102,13 @@ struct bpf_htab {
> bool use_percpu_counter;
> u32 n_buckets; /* number of hash buckets */
> u32 elem_size; /* size of each element in bytes */
> + u32 key_offset; /* offset of key in bytes */
> u32 hashrnd;
> };
>
> /* each htab element is struct htab_elem + key + value */
> -struct htab_elem {
> +struct htab_elem;
> +struct htab_node {
> union {
> struct hlist_nulls_node hash_node;
> struct {
> @@ -117,11 +119,27 @@ struct htab_elem {
> };
> };
> };
> - union {
> - /* pointer to per-cpu pointer */
> - void *ptr_to_pptr;
> - struct bpf_lru_node lru_node;
> - };
> +};
> +
> +struct htab_elem {
> + struct htab_node node;
> + u32 hash;
> + char key[] __aligned(8);
> +};
> +
> +struct htab_elem_lru {
> + struct htab_node node;
> + struct bpf_lru_node lru_node;
> + u32 hash;
> + char key[] __aligned(8);
> +};
> +
> +/* Only for non-preallocated PCPU maps. Preallocated PCPU maps don't need
> + * ptr_to_pptr, and use htab_elem.
> + */
> +struct htab_elem_pcpu {
> + struct htab_node node;
> + void *ptr_to_pptr;
> u32 hash;
> char key[] __aligned(8);
> };
> @@ -136,6 +154,21 @@ static inline bool htab_is_prealloc(const struct bpf_htab *htab)
> return !(htab->map.map_flags & BPF_F_NO_PREALLOC);
> }
>
> +static inline struct bpf_lru_node *htab_elem_lru_node(struct htab_elem *l)
> +{
> + return &((struct htab_elem_lru *)l)->lru_node;
> +}
> +
> +static inline void *htab_elem_get_ptr_to_pptr(struct htab_elem *l)
> +{
> + return ((struct htab_elem_pcpu *)l)->ptr_to_pptr;
> +}
> +
> +static inline void htab_elem_set_ptr_to_pptr(struct htab_elem *l, void *ptr)
> +{
> + ((struct htab_elem_pcpu *)l)->ptr_to_pptr = ptr;
> +}
> +
> static void htab_init_buckets(struct bpf_htab *htab)
> {
> unsigned int i;
> @@ -183,25 +216,30 @@ static inline bool is_fd_htab(const struct bpf_htab *htab)
> return htab->map.map_type == BPF_MAP_TYPE_HASH_OF_MAPS;
> }
>
> -static inline void *htab_elem_value(struct htab_elem *l, u32 key_size)
> +static inline void *htab_elem_key(struct bpf_htab *htab, struct htab_elem *l)
> +{
> + return (void *)l + htab->key_offset;
> +}
> +
> +static inline void *htab_elem_value(struct bpf_htab *htab, struct htab_elem *l)
> {
> - return l->key + round_up(key_size, 8);
> + return htab_elem_key(htab, l) + round_up(htab->map.key_size, 8);
> }
>
> -static inline void htab_elem_set_ptr(struct htab_elem *l, u32 key_size,
> +static inline void htab_elem_set_ptr(struct bpf_htab *htab, struct htab_elem *l,
> void __percpu *pptr)
> {
> - *(void __percpu **)htab_elem_value(l, key_size) = pptr;
> + *(void __percpu **)htab_elem_value(htab, l) = pptr;
> }
>
> -static inline void __percpu *htab_elem_get_ptr(struct htab_elem *l, u32 key_size)
> +static inline void __percpu *htab_elem_get_ptr(struct bpf_htab *htab, struct htab_elem *l)
> {
> - return *(void __percpu **)htab_elem_value(l, key_size);
> + return *(void __percpu **)htab_elem_value(htab, l);
> }
>
> -static void *fd_htab_map_get_ptr(const struct bpf_map *map, struct htab_elem *l)
> +static void *fd_htab_map_get_ptr(struct bpf_htab *htab, struct htab_elem *l)
> {
> - return *(void **)htab_elem_value(l, map->key_size);
> + return *(void **)htab_elem_value(htab, l);
> }
>
> static struct htab_elem *get_htab_elem(struct bpf_htab *htab, int i)
> @@ -209,6 +247,26 @@ static struct htab_elem *get_htab_elem(struct bpf_htab *htab, int i)
> return (struct htab_elem *) (htab->elems + i * (u64)htab->elem_size);
> }
>
> +static inline u32 htab_elem_hash(struct bpf_htab *htab, struct htab_elem *l)
> +{
> + if (htab_is_lru(htab))
> + return ((struct htab_elem_lru *)l)->hash;
> + else if (htab_is_percpu(htab) && !htab_is_prealloc(htab))
> + return ((struct htab_elem_pcpu *)l)->hash;
These casts in getter/setter are a bit annoying, not sure if there is a way to
get rid of them.
> + else
> + return l->hash;
> +}
> +
> +static inline void htab_elem_set_hash(struct bpf_htab *htab, struct htab_elem *l, u32 hash)
> +{
> + if (htab_is_lru(htab))
> + ((struct htab_elem_lru *)l)->hash = hash;
> + else if (htab_is_percpu(htab) && !htab_is_prealloc(htab))
> + ((struct htab_elem_pcpu *)l)->hash = hash;
> + else
> + l->hash = hash;
> +}
> +
...
> htab->extra_elems = pptr;
> @@ -425,8 +481,8 @@ static int htab_map_alloc_check(union bpf_attr *attr)
> bool zero_seed = (attr->map_flags & BPF_F_ZERO_SEED);
> int numa_node = bpf_map_attr_numa_node(attr);
>
> - BUILD_BUG_ON(offsetof(struct htab_elem, fnode.next) !=
> - offsetof(struct htab_elem, hash_node.pprev));
> + BUILD_BUG_ON(offsetof(struct htab_node, fnode.next) !=
> + offsetof(struct htab_node, hash_node.pprev));
>
> if (zero_seed && !capable(CAP_SYS_ADMIN))
> /* Guard against local DoS, and discourage production use. */
> @@ -476,7 +532,7 @@ static void htab_mem_dtor(void *obj, void *ctx)
> if (IS_ERR_OR_NULL(hrec->record))
> return;
>
> - map_value = htab_elem_value(elem, hrec->key_size);
> + map_value = (void *)elem + sizeof(struct htab_elem) + round_up(hrec->key_size, 8);
Why this can't be htab_elem_value()?
> bpf_obj_free_fields(hrec->record, map_value);
> }
>
> @@ -583,8 +639,14 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
>
> htab->n_buckets = roundup_pow_of_two(htab->map.max_entries);
>
> - htab->elem_size = sizeof(struct htab_elem) +
> - round_up(htab->map.key_size, 8);
> + if (htab_is_lru(htab))
> + htab->key_offset = sizeof(struct htab_elem_lru);
nit: I think it'll be nicer to use explicit offsetof().
> + else if (percpu && !prealloc)
> + htab->key_offset = sizeof(struct htab_elem_pcpu);
> + else
> + htab->key_offset = sizeof(struct htab_elem);
> +
> + htab->elem_size = htab->key_offset + round_up(htab->map.key_size, 8);
> if (percpu)
> htab->elem_size += sizeof(void *);
> else
> @@ -692,14 +754,16 @@ static inline struct hlist_nulls_head *select_bucket(struct bpf_htab *htab, u32
> }
>
...
> @@ -912,18 +982,19 @@ static int htab_map_get_next_key(struct bpf_map *map, void *key, void *next_key)
> head = select_bucket(htab, hash);
>
> /* lookup the key */
> - l = lookup_nulls_elem_raw(head, hash, key, key_size, htab->n_buckets);
> + l = lookup_nulls_elem_raw(htab, head, hash, key, key_size, htab->n_buckets);
>
> if (!l)
> goto find_first_elem;
>
> /* key was found, get next key in the same bucket */
> - next_l = hlist_nulls_entry_safe(rcu_dereference_raw(hlist_nulls_next_rcu(&l->hash_node)),
> - struct htab_elem, hash_node);
> + next_l = hlist_nulls_entry_safe(
> + rcu_dereference_raw(hlist_nulls_next_rcu(&l->node.hash_node)),
nit: I think this line has not changed.
> + struct htab_elem, node.hash_node);
>
> if (next_l) {
> /* if next elem in this hash list is non-zero, just return it */
> - memcpy(next_key, next_l->key, key_size);
> + memcpy(next_key, htab_elem_key(htab, next_l), key_size);
> return 0;
> }
> ...
next prev parent reply other threads:[~2026-08-07 15:25 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-05 22:35 [PATCH bpf-next v3 0/2] bpf: htab: Reduce memory use of hash maps T.J. Mercier
2026-08-05 22:35 ` [PATCH bpf-next v3 1/2] bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem T.J. Mercier
2026-08-07 15:25 ` Mykyta Yatsenko [this message]
2026-08-10 20:53 ` T.J. Mercier
2026-08-05 22:35 ` [PATCH bpf-next v3 2/2] bpf: htab: Reduce elem_size by 8 bytes for small key sizes T.J. Mercier
2026-08-05 22:48 ` sashiko-bot
2026-08-07 17:33 ` Mykyta Yatsenko
2026-08-10 20:55 ` T.J. Mercier
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=e417cb09-8466-4d9e-9d33-7330d84df12a@gmail.com \
--to=mykyta.yatsenko5@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=song@kernel.org \
--cc=tjmercier@google.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.