Re: [PATCH bpf-next v3 1/2] bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem

Mykyta Yatsenko <[email protected]>
Newsgroups org.kernel.vger.bpf,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>
> ---
>  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;
>  	}
>  ...
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.