BPF List
 help / color / mirror / Atom feed
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;
>  	}
>  ...

  reply	other threads:[~2026-08-07 15:25 UTC|newest]

Thread overview: 6+ 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-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

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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox