* [PATCH bpf-next v5 0/2] bpf: htab: Reduce memory use of hash maps
@ 2026-09-21 0:22 T.J. Mercier
2026-09-21 0:22 ` [PATCH bpf-next v5 1/2] bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem T.J. Mercier
2026-09-21 0:22 ` [PATCH bpf-next v5 2/2] bpf: htab: Reduce elem_size by 8 bytes for small key sizes T.J. Mercier
0 siblings, 2 replies; 5+ messages in thread
From: T.J. Mercier @ 2026-09-21 0:22 UTC (permalink / raw)
To: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, mykyta.yatsenko5
Cc: bpf, linux-kernel, T.J. Mercier
Memory is expensive and scarce these days. This series reduces the
memory use of BPF hash maps by eliminating the per-element overheads
below. This saves up to 50% of per-element memory use for standard and
PCPU hash maps. The memory use of LRU hash maps is unaffected.
Map Type & Configuration | Old size | New size | Savings
-----------------------------------|----------|----------|--------
Standard (key ≤ 8 B, val ≤ 8 B) | 64 B | 32 B | 50.0%
Per-CPU (prealloc) (key ≤ 8 B) | 64 B | 32 B | 50.0%
Per-CPU (non-prealloc) (key ≤ 8 B) | 64 B | 40 B | 37.5%
LRU (Any key/value size) | - | - | 00.0%
1) Unused LRU / PCPU fields in standard and PCPU hash maps (patch 1)
struct htab_elem is used for all hash map types, and includes fields
that are not always used (bpf_lru_node, ptr_to_pptr). For standard
(non-LRU, non-PCPU) hash maps the 24 bytes for the bpf_lru_node (union)
are entirely overhead and can be eliminated. Non-preallocated PCPU maps
only need the 8 byte ptr_to_pptr which is currently unioned with the
unneeded 24 byte bpf_lru_node, so 16 bytes of overhead can be
eliminated. Preallocated PCPU maps don't need ptr_to_pptr, so 24 bytes
of overhead can be saved.
2) Hash caching for small keys (patch 2)
For hash maps with small key sizes (≤ word size), comparing keys only
requires a single instruction. Currently the 4 byte hash value (8 byte
aligned and padded) is used for this, but offers no performance
advantage in this case and can be eliminated.
The implementation splits htab_elem into dedicated structures for the
different map types (htab_elem, htab_elem_pcpu, htab_elem_lru with
hashed and unhashed variants) so that the map-type specific fields
exist only in structures where they are necessary. In all hashed
variants, the hash is always at a -8 byte offset from the start of
htab_elem. The elem_offset map field supports the different sized
element headers, and allows dynamic offsets to be kept out of the hot
lookup path. It is used primarliy for allocation / free, and indexing
preallocated elements.
run_bench_htab_mem.sh shows the following changes across 10 runs on my
3995WX.
Benchmark (all in kops/sec) | Avg. Before | Avg. After | Delta
-----------------------------|---------------|--------------|--------
prealloc overwrite | 116.26 ± 4.3 | 118.81 ± 4.2 | +2.19%
prealloc batch_add_batch_del | 128.43 ± 3.6 | 128.89 ± 2.9 | +0.35%
prealloc add_del_on_diff_cpu | 22.91 ± 0.69 | 22.44 ± 0.62 | -2.07%
normal overwrite | 72.43 ± 3.13 | 81.06 ± 1.49 | +11.9%
normal batch_add_batch_del | 45.20 ± 0.78 | 50.09 ± 0.56 | +10.8%
normal add_del_on_diff_cpu | 12.02 ± 0.24 | 12.80 ± 0.18 | +6.49%
---
Changes in v5:
Rebase on top of bpf-next/for-next
Update elem_size check in map_ptr_kern selftest to 40 in first patch
(Sashiko)
Place hash at constant compile-time offset before htab_elem, to allow
removal of key_offset. (Andrii Nakryiko)
This avoids dynamic htab->key_offset load and pointer arithmetic. The
struct declarations got reworked to implement this, and htab_node was
dropped. All map_in_map changes became unnecessary and were dropped.
Fix key update before pptr, value initialization for recycled elements
and lockless readers. (Sashiko finding on internal run)
Read / write memory barriers and READ_ONCE() / WRITE_ONCE() were added
for this.
Combine rhtab_mem_dtor() and htab_mem_dtor() implementations.
Fixed the KMALLOC_MAX_SIZE overflow check since sizeof(struct htab_elem)
shrinks in both patches.
Changes in v4:
Removed inline from new functions per BPF CI (netdev/source_inline).
From Mykyta Yatsenko:
Factor out duplicate lookup_elem code into __lookup_elem_raw.
Use offsetof instead of sizeof for key_offset assignments in
htab_map_alloc (patch 1).
Eliminate branching and htab_elem casting in htab_elem_hash /
htab_elem_set_hash.
Changes in v3:
From Sashiko on torn reads/writes:
Use a local unsigned long and READ_ONCE / WRITE_ONCE instead of memcmp /
memcpy for atomic key comparisons for hashless elements.
Changes in v2:
Make maximum key_size for !has_hash depend on word size for atomicity
on 32-bit.
From Mykyta Yatsenko:
Put the htab_elem* common initial sequence in its own struct (htab_node)
and reuse it across all element types that share it. Eliminate
associated BUILD_BUG_ON additions.
Replace both the hash and key fields with data[].
Store has_hash in struct bpf_htab, and avoid per-element reads of it.
T.J. Mercier (2):
bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem
bpf: htab: Reduce elem_size by 8 bytes for small key sizes
kernel/bpf/hashtab.c | 351 +++++++++++++-----
.../selftests/bpf/progs/map_ptr_kern.c | 2 +-
2 files changed, 255 insertions(+), 98 deletions(-)
base-commit: b99f71407ce529ba01a9392f477522d2e76c6613
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v5 1/2] bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem
2026-09-21 0:22 [PATCH bpf-next v5 0/2] bpf: htab: Reduce memory use of hash maps T.J. Mercier
@ 2026-09-21 0:22 ` T.J. Mercier
2026-09-21 0:51 ` Alexei Starovoitov
2026-09-21 0:22 ` [PATCH bpf-next v5 2/2] bpf: htab: Reduce elem_size by 8 bytes for small key sizes T.J. Mercier
1 sibling, 1 reply; 5+ messages in thread
From: T.J. Mercier @ 2026-09-21 0:22 UTC (permalink / raw)
To: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, mykyta.yatsenko5
Cc: bpf, linux-kernel, T.J. Mercier
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.
Place lru_node and ptr_to_pptr before struct htab_elem in
htab_elem_lru and htab_elem_pcpu respectively, and track the offset of
htab_elem from the start of the element allocation in htab->elem_offset.
This keeps key and value at constant compile-time offsets from struct
htab_elem across all hash map types, avoiding dynamic key offset
calculations on lookups.
All element variants get added to the htab_elem_all union to support the
element size rollover check in htab_map_alloc_check().
Signed-off-by: T.J. Mercier <tjmercier@google.com>
---
kernel/bpf/hashtab.c | 164 ++++++++++++------
.../selftests/bpf/progs/map_ptr_kern.c | 2 +-
2 files changed, 114 insertions(+), 52 deletions(-)
diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c
index 6f331c80130d..905bddadf37f 100644
--- a/kernel/bpf/hashtab.c
+++ b/kernel/bpf/hashtab.c
@@ -102,6 +102,7 @@ struct bpf_htab {
bool use_percpu_counter;
u32 n_buckets; /* number of hash buckets */
u32 elem_size; /* size of each element in bytes */
+ u32 elem_offset;/* offset of htab_elem in bytes */
u32 hashrnd;
};
@@ -117,15 +118,30 @@ struct htab_elem {
};
};
};
- union {
- /* pointer to per-cpu pointer */
- void *ptr_to_pptr;
- struct bpf_lru_node lru_node;
- };
- u32 hash;
+ u32 hash __aligned(8);
char key[] __aligned(8);
};
+struct htab_elem_lru {
+ struct bpf_lru_node lru_node;
+ struct htab_elem elem;
+};
+
+/*
+ * Only for non-preallocated PCPU maps. Preallocated PCPU maps don't need
+ * ptr_to_pptr, and use htab_elem.
+ */
+struct htab_elem_pcpu {
+ void *ptr_to_pptr;
+ struct htab_elem elem;
+};
+
+union htab_elem_all {
+ struct htab_elem elem;
+ struct htab_elem_lru lru;
+ struct htab_elem_pcpu pcpu;
+};
+
struct htab_btf_record {
struct btf_record *record;
u32 key_size;
@@ -183,6 +199,21 @@ static inline bool is_fd_htab(const struct bpf_htab *htab)
return htab->map.map_type == BPF_MAP_TYPE_HASH_OF_MAPS;
}
+static void *htab_elem_container(const struct bpf_htab *htab, struct htab_elem *l)
+{
+ return (void *)l - htab->elem_offset;
+}
+
+static void *htab_elem_get_ptr_to_pptr(struct htab_elem *l)
+{
+ return container_of(l, struct htab_elem_pcpu, elem)->ptr_to_pptr;
+}
+
+static void htab_elem_set_ptr_to_pptr(struct htab_elem *l, void *ptr)
+{
+ container_of(l, struct htab_elem_pcpu, elem)->ptr_to_pptr = ptr;
+}
+
static inline void *htab_elem_value(struct htab_elem *l, u32 key_size)
{
return l->key + round_up(key_size, 8);
@@ -206,7 +237,7 @@ static void *fd_htab_map_get_ptr(const struct bpf_map *map, struct htab_elem *l)
static struct htab_elem *get_htab_elem(struct bpf_htab *htab, int i)
{
- return (struct htab_elem *) (htab->elems + i * (u64)htab->elem_size);
+ return htab->elems + i * (u64)htab->elem_size + htab->elem_offset;
}
/* Both percpu and fd htab support in-place update, so no need for
@@ -300,16 +331,16 @@ static void htab_free_elems(struct bpf_htab *htab)
* bucket_lock followed by lru_lock is not allowed. In such cases,
* bucket_lock needs to be released first before acquiring lru_lock.
*/
-static struct htab_elem *prealloc_lru_pop(struct bpf_htab *htab, void *key,
- u32 hash)
+static struct htab_elem_lru *prealloc_lru_pop(struct bpf_htab *htab, void *key,
+ u32 hash)
{
struct bpf_lru_node *node = bpf_lru_pop_free(&htab->lru, hash);
- struct htab_elem *l;
+ struct htab_elem_lru *l;
if (node) {
bpf_map_inc_elem_count(&htab->map);
- l = container_of(node, struct htab_elem, lru_node);
- memcpy(l->key, key, htab->map.key_size);
+ l = container_of(node, struct htab_elem_lru, lru_node);
+ memcpy(l->elem.key, key, htab->map.key_size);
return l;
}
@@ -349,8 +380,8 @@ static int prealloc_init(struct bpf_htab *htab)
if (htab_is_lru(htab))
err = bpf_lru_init(&htab->lru,
htab->map.map_flags & BPF_F_NO_COMMON_LRU,
- offsetof(struct htab_elem, hash) -
- offsetof(struct htab_elem, lru_node),
+ offsetof(struct htab_elem_lru, elem.hash) -
+ offsetof(struct htab_elem_lru, lru_node),
htab_lru_map_delete_node,
htab);
else
@@ -361,11 +392,12 @@ static int prealloc_init(struct bpf_htab *htab)
if (htab_is_lru(htab))
bpf_lru_populate(&htab->lru, htab->elems,
- offsetof(struct htab_elem, lru_node),
+ offsetof(struct htab_elem_lru, lru_node),
htab->elem_size, num_entries);
else
pcpu_freelist_populate(&htab->freelist,
- htab->elems + offsetof(struct htab_elem, fnode),
+ htab->elems + htab->elem_offset +
+ offsetof(struct htab_elem, fnode),
htab->elem_size, num_entries);
return 0;
@@ -452,8 +484,8 @@ static int htab_map_alloc_check(union bpf_attr *attr)
attr->value_size == 0)
return -EINVAL;
- if ((u64)attr->key_size + attr->value_size >= KMALLOC_MAX_SIZE -
- sizeof(struct htab_elem))
+ if (round_up((u64)attr->key_size, 8) + round_up((u64)attr->value_size, 8) >=
+ KMALLOC_MAX_SIZE - sizeof(union htab_elem_all))
/* if key_size + value_size is bigger, the user space won't be
* able to access the elements via bpf syscall. This check
* also makes sure that the elem_size doesn't overflow and it's
@@ -556,6 +588,7 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
*/
bool percpu_lru = (attr->map_flags & BPF_F_NO_COMMON_LRU);
bool prealloc = !(attr->map_flags & BPF_F_NO_PREALLOC);
+ u32 elem_offset = 0;
struct bpf_htab *htab;
int err;
@@ -586,7 +619,17 @@ 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) +
+ if (htab_is_lru(htab))
+ elem_offset = offsetof(struct htab_elem_lru, elem);
+ else if (percpu && !prealloc)
+ elem_offset = offsetof(struct htab_elem_pcpu, elem);
+
+ BUILD_BUG_ON(elem_offset + sizeof(struct htab_elem) >
+ sizeof(union htab_elem_all));
+ htab->elem_offset = elem_offset;
+
+ htab->elem_size = htab->elem_offset +
+ sizeof(struct htab_elem) +
round_up(htab->map.key_size, 8);
if (percpu)
htab->elem_size += sizeof(void *);
@@ -797,8 +840,12 @@ static __always_inline void *__htab_lru_map_lookup_elem(struct bpf_map *map,
struct htab_elem *l = __htab_map_lookup_elem(map, key);
if (l) {
- if (mark)
- bpf_lru_node_set_ref(&l->lru_node);
+ if (mark) {
+ struct htab_elem_lru *l_lru =
+ container_of(l, struct htab_elem_lru, elem);
+
+ bpf_lru_node_set_ref(&l_lru->lru_node);
+ }
return htab_elem_value(l, map->key_size);
}
@@ -821,19 +868,17 @@ static int htab_lru_map_gen_lookup(struct bpf_map *map,
struct bpf_insn *insn = insn_buf;
const int ret = BPF_REG_0;
const int ref_reg = BPF_REG_1;
+ const s16 ref_off = (int)offsetof(struct htab_elem_lru, lru_node) +
+ (int)offsetof(struct bpf_lru_node, ref) -
+ (int)offsetof(struct htab_elem_lru, elem);
BUILD_BUG_ON(!__same_type(&__htab_map_lookup_elem,
(void *(*)(struct bpf_map *map, void *key))NULL));
*insn++ = BPF_EMIT_CALL(__htab_map_lookup_elem);
*insn++ = BPF_JMP_IMM(BPF_JEQ, ret, 0, 4);
- *insn++ = BPF_LDX_MEM(BPF_B, ref_reg, ret,
- offsetof(struct htab_elem, lru_node) +
- offsetof(struct bpf_lru_node, ref));
+ *insn++ = BPF_LDX_MEM(BPF_B, ref_reg, ret, ref_off);
*insn++ = BPF_JMP_IMM(BPF_JNE, ref_reg, 0, 1);
- *insn++ = BPF_ST_MEM(BPF_B, ret,
- offsetof(struct htab_elem, lru_node) +
- offsetof(struct bpf_lru_node, ref),
- 1);
+ *insn++ = BPF_ST_MEM(BPF_B, ret, ref_off, 1);
*insn++ = BPF_ALU64_IMM(BPF_ADD, ret,
offsetof(struct htab_elem, key) +
round_up(map->key_size, 8));
@@ -865,15 +910,16 @@ static void check_and_cancel_fields(struct bpf_htab *htab,
static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node)
{
struct bpf_htab *htab = arg;
- struct htab_elem *l = NULL, *tgt_l;
+ struct htab_elem_lru *tgt_l;
+ struct htab_elem *l = NULL;
struct hlist_nulls_head *head;
struct hlist_nulls_node *n;
unsigned long flags;
struct bucket *b;
int ret;
- tgt_l = container_of(node, struct htab_elem, lru_node);
- b = __select_bucket(htab, tgt_l->hash);
+ tgt_l = container_of(node, struct htab_elem_lru, lru_node);
+ b = __select_bucket(htab, tgt_l->elem.hash);
head = &b->head;
ret = htab_lock_bucket(b, &flags);
@@ -881,7 +927,7 @@ static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node)
return false;
hlist_nulls_for_each_entry_rcu(l, n, head, hash_node)
- if (l == tgt_l) {
+ if (l == &tgt_l->elem) {
hlist_nulls_del_rcu(&l->hash_node);
bpf_map_dec_elem_count(&htab->map);
break;
@@ -889,9 +935,9 @@ static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node)
htab_unlock_bucket(b, flags);
- if (l == tgt_l)
+ if (l == &tgt_l->elem)
check_and_cancel_fields(htab, l);
- return l == tgt_l;
+ return l == &tgt_l->elem;
}
/* Called from syscall */
@@ -958,8 +1004,8 @@ static void htab_elem_free(struct bpf_htab *htab, struct htab_elem *l)
check_and_cancel_fields(htab, l);
if (htab->map.map_type == BPF_MAP_TYPE_PERCPU_HASH)
- bpf_mem_cache_free(&htab->pcpu_ma, l->ptr_to_pptr);
- bpf_mem_cache_free(&htab->ma, l);
+ bpf_mem_cache_free(&htab->pcpu_ma, htab_elem_get_ptr_to_pptr(l));
+ bpf_mem_cache_free(&htab->ma, htab_elem_container(htab, l));
}
static void htab_put_fd_value(struct bpf_htab *htab, struct htab_elem *l)
@@ -1104,6 +1150,8 @@ static struct htab_elem *alloc_htab_elem(struct bpf_htab *htab, void *key,
bpf_map_inc_elem_count(&htab->map);
}
} else {
+ void *container;
+
if (is_map_full(htab))
if (!old_elem)
/* when map is full and update() is replacing
@@ -1113,11 +1161,12 @@ static struct htab_elem *alloc_htab_elem(struct bpf_htab *htab, void *key,
*/
return ERR_PTR(-E2BIG);
inc_elem_count(htab);
- l_new = bpf_mem_cache_alloc(&htab->ma);
- if (!l_new) {
+ container = bpf_mem_cache_alloc(&htab->ma);
+ if (!container) {
l_new = ERR_PTR(-ENOMEM);
goto dec_count;
}
+ l_new = container + htab->elem_offset;
}
memcpy(l_new->key, key, key_size);
@@ -1129,11 +1178,11 @@ static struct htab_elem *alloc_htab_elem(struct bpf_htab *htab, void *key,
void *ptr = bpf_mem_cache_alloc(&htab->pcpu_ma);
if (!ptr) {
- bpf_mem_cache_free(&htab->ma, l_new);
+ bpf_mem_cache_free(&htab->ma, htab_elem_container(htab, l_new));
l_new = ERR_PTR(-ENOMEM);
goto dec_count;
}
- l_new->ptr_to_pptr = ptr;
+ htab_elem_set_ptr_to_pptr(l_new, ptr);
pptr = *(void __percpu **)ptr;
}
@@ -1276,16 +1325,19 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
static void htab_lru_push_free(struct bpf_htab *htab, struct htab_elem *elem)
{
+ struct htab_elem_lru *l = container_of(elem, struct htab_elem_lru, elem);
+
check_and_cancel_fields(htab, elem);
bpf_map_dec_elem_count(&htab->map);
- bpf_lru_push_free(&htab->lru, &elem->lru_node);
+ bpf_lru_push_free(&htab->lru, &l->lru_node);
}
static long htab_lru_map_update_elem(struct bpf_map *map, void *key, void *value,
u64 map_flags)
{
struct bpf_htab *htab = container_of(map, struct bpf_htab, map);
- struct htab_elem *l_new, *l_old = NULL;
+ struct htab_elem *l_old = NULL;
+ struct htab_elem_lru *l_new;
struct hlist_nulls_head *head;
unsigned long flags;
struct bucket *b;
@@ -1313,7 +1365,7 @@ static long htab_lru_map_update_elem(struct bpf_map *map, void *key, void *value
l_new = prealloc_lru_pop(htab, key, hash);
if (!l_new)
return -ENOMEM;
- copy_map_value(&htab->map, htab_elem_value(l_new, map->key_size), value);
+ copy_map_value(&htab->map, htab_elem_value(&l_new->elem, map->key_size), value);
ret = htab_lock_bucket(b, &flags);
if (ret)
@@ -1328,7 +1380,7 @@ static long htab_lru_map_update_elem(struct bpf_map *map, void *key, void *value
/* add new element to the head of the list, so that
* concurrent search will find it before old elem
*/
- hlist_nulls_add_head_rcu(&l_new->hash_node, head);
+ hlist_nulls_add_head_rcu(&l_new->elem.hash_node, head);
if (l_old) {
bpf_lru_node_set_ref(&l_new->lru_node);
hlist_nulls_del_rcu(&l_old->hash_node);
@@ -1340,7 +1392,7 @@ static long htab_lru_map_update_elem(struct bpf_map *map, void *key, void *value
err_lock_bucket:
if (ret)
- htab_lru_push_free(htab, l_new);
+ htab_lru_push_free(htab, &l_new->elem);
else if (l_old)
htab_lru_push_free(htab, l_old);
@@ -1424,7 +1476,8 @@ static long __htab_lru_percpu_map_update_elem(struct bpf_map *map, void *key,
bool onallcpus)
{
struct bpf_htab *htab = container_of(map, struct bpf_htab, map);
- struct htab_elem *l_new = NULL, *l_old;
+ struct htab_elem_lru *l_new = NULL;
+ struct htab_elem *l_old;
struct hlist_nulls_head *head;
unsigned long flags;
struct bucket *b;
@@ -1466,15 +1519,18 @@ static long __htab_lru_percpu_map_update_elem(struct bpf_map *map, void *key,
goto err;
if (l_old) {
- bpf_lru_node_set_ref(&l_old->lru_node);
+ struct htab_elem_lru *l_old_lru =
+ container_of(l_old, struct htab_elem_lru, elem);
+
+ bpf_lru_node_set_ref(&l_old_lru->lru_node);
/* per-cpu hash map can update value in-place */
pcpu_copy_value(htab, htab_elem_get_ptr(l_old, key_size),
value, onallcpus, map_flags);
} else {
- pcpu_init_value(htab, htab_elem_get_ptr(l_new, key_size),
+ pcpu_init_value(htab, htab_elem_get_ptr(&l_new->elem, key_size),
value, onallcpus, map_flags);
- hlist_nulls_add_head_rcu(&l_new->hash_node, head);
+ hlist_nulls_add_head_rcu(&l_new->elem.hash_node, head);
l_new = NULL;
}
ret = 0;
@@ -2453,7 +2509,10 @@ static void *htab_lru_percpu_map_lookup_elem(struct bpf_map *map, void *key)
struct htab_elem *l = __htab_map_lookup_elem(map, key);
if (l) {
- bpf_lru_node_set_ref(&l->lru_node);
+ struct htab_elem_lru *l_lru =
+ container_of(l, struct htab_elem_lru, elem);
+
+ bpf_lru_node_set_ref(&l_lru->lru_node);
return this_cpu_ptr(htab_elem_get_ptr(l, map->key_size));
}
@@ -2469,7 +2528,10 @@ static void *htab_lru_percpu_map_lookup_percpu_elem(struct bpf_map *map, void *k
l = __htab_map_lookup_elem(map, key);
if (l) {
- bpf_lru_node_set_ref(&l->lru_node);
+ struct htab_elem_lru *l_lru =
+ container_of(l, struct htab_elem_lru, elem);
+
+ bpf_lru_node_set_ref(&l_lru->lru_node);
return per_cpu_ptr(htab_elem_get_ptr(l, map->key_size), cpu);
}
diff --git a/tools/testing/selftests/bpf/progs/map_ptr_kern.c b/tools/testing/selftests/bpf/progs/map_ptr_kern.c
index 373c8d17ea55..f71be4fc8dd7 100644
--- a/tools/testing/selftests/bpf/progs/map_ptr_kern.c
+++ b/tools/testing/selftests/bpf/progs/map_ptr_kern.c
@@ -114,7 +114,7 @@ static inline int check_hash(void)
VERIFY(check_default_noinline(&hash->map, map));
VERIFY(hash->n_buckets == MAX_ENTRIES);
- VERIFY(hash->elem_size == 64);
+ VERIFY(hash->elem_size == 40);
VERIFY(hash->count.counter == 0);
VERIFY(bpf_map_sum_elem_count(map) == 0);
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v5 2/2] bpf: htab: Reduce elem_size by 8 bytes for small key sizes
2026-09-21 0:22 [PATCH bpf-next v5 0/2] bpf: htab: Reduce memory use of hash maps T.J. Mercier
2026-09-21 0:22 ` [PATCH bpf-next v5 1/2] bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem T.J. Mercier
@ 2026-09-21 0:22 ` T.J. Mercier
2026-09-21 0:57 ` Alexei Starovoitov
1 sibling, 1 reply; 5+ messages in thread
From: T.J. Mercier @ 2026-09-21 0:22 UTC (permalink / raw)
To: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, mykyta.yatsenko5
Cc: bpf, linux-kernel, T.J. Mercier
For standard and PCPU (non-LRU) hash maps with small key sizes (less
than or equal to the word size), comparing keys requires only a single
instruction. Storing a cached 32-bit hash value to shortcut full key
comparisons provides no performance advantage for small keys, and
consumes memory for every element.
This memory can be saved by removing hash from struct htab_elem and
placing it directly before struct htab_elem only for the new
htab_elem_hashed type which is used only when keys are larger than the
word size or for LRU maps. This reduces elem_size by 8 bytes for small
keys while keeping key and hash at constant compile-time offsets from
struct htab_elem across all map types and key sizes.
All element variants requiring hashes get an anonymous
htab_elem_hashed embedding, ensuring that the -8 byte hash offset is
guaranteed for all element types by composition.
Elements can be recycled without a RCU grace period. Before this commit,
alloc_htab_elem() overwrote the key before initializing the value/pptr
and wrote the hash last, so during value assignment on a recycled
element, the new key was paired with the old hash preventing lockless
readers from matching either the old key or the new key except when
there was also a hash collision.
When hashes are omitted for small keys, only the key field guards
lookups. Writing the new key before value/pptr initialization would
allow a concurrent lookup of the new key to match an uninitialized value
or dereference a stale or freed pptr. So move the new key assignment to
the end of alloc_htab_elem() (where the hash assignment was done) behind
a write memory barrier.
For recycled elements, keeping the old key until the new value is
written slightly widens the existing window where a lockless lookup
racing with a deletion of the old key can observe the recycled element's
new value.
Before this commit htab_mem_dtor() was invoked with the start of the
allocation, which is no longer always the address of struct htab_elem
now that elem_offset can be nonzero for non-preallocated, non-per-CPU
maps. So the key_size field of struct htab_btf_record has been replaced
with a value_offset computed at map creation and passed through
bpf_ma_set_dtor(). That reduces the destructor to a call to
bpf_obj_free_fields() at a fixed offset, which is exactly what
rhtab_mem_dtor() did, so rhtab_mem_dtor() has been removed and replaced
with the new implementation of htab_mem_dtor() for BPF_MAP_TYPE_RHASH.
Together with the previous patch, this reduces the minimum standard and
preallocated hash map element size from 64 bytes down to 32 bytes, and
non-preallocated per-CPU element size from 64 bytes down to 40 bytes.
Signed-off-by: T.J. Mercier <tjmercier@google.com>
---
kernel/bpf/hashtab.c | 209 +++++++++++++-----
.../selftests/bpf/progs/map_ptr_kern.c | 2 +-
2 files changed, 153 insertions(+), 58 deletions(-)
diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c
index 905bddadf37f..5e916ccb53e0 100644
--- a/kernel/bpf/hashtab.c
+++ b/kernel/bpf/hashtab.c
@@ -100,6 +100,7 @@ struct bpf_htab {
struct percpu_counter pcount;
atomic_t count;
bool use_percpu_counter;
+ bool has_hash;
u32 n_buckets; /* number of hash buckets */
u32 elem_size; /* size of each element in bytes */
u32 elem_offset;/* offset of htab_elem in bytes */
@@ -118,13 +119,17 @@ struct htab_elem {
};
};
};
- u32 hash __aligned(8);
char key[] __aligned(8);
};
+struct htab_elem_hashed {
+ u32 hash __aligned(8);
+ struct htab_elem elem;
+};
+
struct htab_elem_lru {
struct bpf_lru_node lru_node;
- struct htab_elem elem;
+ struct htab_elem_hashed;
};
/*
@@ -136,15 +141,29 @@ struct htab_elem_pcpu {
struct htab_elem elem;
};
+/*
+ * Only for non-preallocated PCPU maps. Preallocated PCPU maps don't need
+ * ptr_to_pptr, and use htab_elem_hashed.
+ */
+struct htab_elem_pcpu_hashed {
+ void *ptr_to_pptr;
+ struct htab_elem_hashed;
+};
+
+static_assert(offsetof(struct htab_elem_pcpu_hashed, ptr_to_pptr) ==
+ offsetof(struct htab_elem_pcpu, ptr_to_pptr));
+
union htab_elem_all {
struct htab_elem elem;
+ struct htab_elem_hashed hashed;
struct htab_elem_lru lru;
struct htab_elem_pcpu pcpu;
+ struct htab_elem_pcpu_hashed pcpu_hashed;
};
struct htab_btf_record {
struct btf_record *record;
- u32 key_size;
+ u32 value_offset;
};
static inline bool htab_is_prealloc(const struct bpf_htab *htab)
@@ -199,19 +218,40 @@ static inline bool is_fd_htab(const struct bpf_htab *htab)
return htab->map.map_type == BPF_MAP_TYPE_HASH_OF_MAPS;
}
+static bool htab_has_hash(const struct bpf_htab *htab)
+{
+ return htab->has_hash;
+}
+
+static u32 htab_elem_hash(struct htab_elem *l)
+{
+ return READ_ONCE(container_of(l, struct htab_elem_hashed, elem)->hash);
+}
+
+static void htab_elem_set_hash(struct bpf_htab *htab, struct htab_elem *l, u32 hash)
+{
+ if (htab_has_hash(htab))
+ WRITE_ONCE(container_of(l, struct htab_elem_hashed, elem)->hash, hash);
+}
+
static void *htab_elem_container(const struct bpf_htab *htab, struct htab_elem *l)
{
return (void *)l - htab->elem_offset;
}
-static void *htab_elem_get_ptr_to_pptr(struct htab_elem *l)
+static void *htab_elem_get_ptr_to_pptr(const struct bpf_htab *htab, struct htab_elem *l)
{
- return container_of(l, struct htab_elem_pcpu, elem)->ptr_to_pptr;
+ struct htab_elem_pcpu *pcpu_elem = htab_elem_container(htab, l);
+
+ return pcpu_elem->ptr_to_pptr;
}
-static void htab_elem_set_ptr_to_pptr(struct htab_elem *l, void *ptr)
+static void htab_elem_set_ptr_to_pptr(const struct bpf_htab *htab, struct htab_elem *l,
+ void *ptr)
{
- container_of(l, struct htab_elem_pcpu, elem)->ptr_to_pptr = ptr;
+ struct htab_elem_pcpu *pcpu_elem = htab_elem_container(htab, l);
+
+ pcpu_elem->ptr_to_pptr = ptr;
}
static inline void *htab_elem_value(struct htab_elem *l, u32 key_size)
@@ -380,7 +420,7 @@ static int prealloc_init(struct bpf_htab *htab)
if (htab_is_lru(htab))
err = bpf_lru_init(&htab->lru,
htab->map.map_flags & BPF_F_NO_COMMON_LRU,
- offsetof(struct htab_elem_lru, elem.hash) -
+ offsetof(struct htab_elem_lru, hash) -
offsetof(struct htab_elem_lru, lru_node),
htab_lru_map_delete_node,
htab);
@@ -502,14 +542,11 @@ static int htab_map_alloc_check(union bpf_attr *attr)
static void htab_mem_dtor(void *obj, void *ctx)
{
struct htab_btf_record *hrec = ctx;
- struct htab_elem *elem = obj;
- void *map_value;
if (IS_ERR_OR_NULL(hrec->record))
return;
- map_value = htab_elem_value(elem, hrec->key_size);
- bpf_obj_free_fields(hrec->record, map_value);
+ bpf_obj_free_fields(hrec->record, obj + hrec->value_offset);
}
static void htab_pcpu_mem_dtor(void *obj, void *ctx)
@@ -534,7 +571,7 @@ static void htab_dtor_ctx_free(void *ctx)
}
static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma,
- void (*dtor)(void *, void *))
+ void (*dtor)(void *, void *), u32 value_offset)
{
struct htab_btf_record *hrec;
int err;
@@ -546,7 +583,7 @@ static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma,
hrec = kzalloc_obj(*hrec);
if (!hrec)
return -ENOMEM;
- hrec->key_size = map->key_size;
+ hrec->value_offset = value_offset;
hrec->record = btf_record_dup(map->record);
if (IS_ERR(hrec->record)) {
err = PTR_ERR(hrec->record);
@@ -572,9 +609,12 @@ static int htab_map_check_btf(struct bpf_map *map, const struct btf *btf,
* populated in htab_map_alloc(), so it will always appear as NULL.
*/
if (htab_is_percpu(htab))
- return bpf_ma_set_dtor(map, &htab->pcpu_ma, htab_pcpu_mem_dtor);
+ return bpf_ma_set_dtor(map, &htab->pcpu_ma, htab_pcpu_mem_dtor, 0);
else
- return bpf_ma_set_dtor(map, &htab->ma, htab_mem_dtor);
+ return bpf_ma_set_dtor(map, &htab->ma, htab_mem_dtor,
+ htab->elem_offset +
+ offsetof(struct htab_elem, key) +
+ round_up(map->key_size, 8));
}
static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
@@ -598,6 +638,14 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
bpf_map_init_from_attr(&htab->map, attr);
+ /*
+ * Avoid hash memory use and comparisons where unnecessary.
+ * u32 hash reads are always atomic. If we elide them, key comparisons must also be atomic
+ * to avoid false positive key matches due to torn key reads / writes. This is only possible
+ * when the key fits within a word, so check key_size.
+ */
+ htab->has_hash = htab_is_lru(htab) || htab->map.key_size > sizeof(unsigned long);
+
if (percpu_lru) {
/* ensure each CPU's lru list has >=1 elements.
* since we are at it, make each lru list has the same
@@ -622,7 +670,11 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
if (htab_is_lru(htab))
elem_offset = offsetof(struct htab_elem_lru, elem);
else if (percpu && !prealloc)
- elem_offset = offsetof(struct htab_elem_pcpu, elem);
+ elem_offset = htab_has_hash(htab) ?
+ offsetof(struct htab_elem_pcpu_hashed, elem) :
+ offsetof(struct htab_elem_pcpu, elem);
+ else if (htab_has_hash(htab))
+ elem_offset = offsetof(struct htab_elem_hashed, elem);
BUILD_BUG_ON(elem_offset + sizeof(struct htab_elem) >
sizeof(union htab_elem_all));
@@ -737,35 +789,67 @@ static inline struct hlist_nulls_head *select_bucket(struct bpf_htab *htab, u32
return &__select_bucket(htab, hash)->head;
}
-/* this lookup function can only be called with bucket lock taken */
-static struct htab_elem *lookup_elem_raw(struct hlist_nulls_head *head, u32 hash,
- void *key, u32 key_size)
+static __always_inline struct htab_elem *
+__lookup_elem_raw(struct bpf_htab *htab, struct hlist_nulls_head *head,
+ u32 hash, void *key, u32 key_size,
+ struct hlist_nulls_node **out_n)
{
struct hlist_nulls_node *n;
struct htab_elem *l;
- hlist_nulls_for_each_entry_rcu(l, n, head, hash_node)
- if (l->hash == hash && !memcmp(&l->key, key, key_size))
- return l;
+ if (htab_has_hash(htab)) {
+ hlist_nulls_for_each_entry_rcu(l, n, head, hash_node)
+ if (htab_elem_hash(l) == hash &&
+ !memcmp(&l->key, key, key_size))
+ return l;
+ } else {
+ /*
+ * When hash is omitted, key comparisons must be atomic. Zero extend
+ * the caller's key to the word size to support an atomic compare.
+ */
+ unsigned long k = 0;
+
+ memcpy(&k, key, key_size);
+ hlist_nulls_for_each_entry_rcu(l, n, head, hash_node)
+ if (READ_ONCE(*(unsigned long *)l->key) == k)
+ return l;
+ }
+ if (out_n)
+ *out_n = n;
return NULL;
}
+/* this lookup function can only be called with bucket lock taken */
+static __always_inline struct htab_elem *
+lookup_elem_raw(struct bpf_htab *htab, struct hlist_nulls_head *head, u32 hash,
+ void *key, u32 key_size)
+{
+ return __lookup_elem_raw(htab, head, hash, key, key_size, NULL);
+}
+
/* can be called without bucket lock. it will repeat the loop in
* the unlikely event when elements moved from one bucket into another
* while link list is being walked
*/
-static struct htab_elem *lookup_nulls_elem_raw(struct hlist_nulls_head *head,
- u32 hash, void *key,
- u32 key_size, u32 n_buckets)
+static __always_inline struct htab_elem *
+lookup_nulls_elem_raw(struct bpf_htab *htab, struct hlist_nulls_head *head,
+ u32 hash, void *key, u32 key_size, u32 n_buckets)
{
struct hlist_nulls_node *n;
struct htab_elem *l;
again:
- hlist_nulls_for_each_entry_rcu(l, n, head, hash_node)
- if (l->hash == hash && !memcmp(&l->key, key, key_size))
- return l;
+ l = __lookup_elem_raw(htab, head, hash, key, key_size, &n);
+ if (l) {
+ /*
+ * Pairs with smp_wmb() in alloc_htab_elem() to ensure
+ * value/pptr reads happen after key/hash match on
+ * recycled elements.
+ */
+ smp_rmb();
+ return l;
+ }
if (unlikely(get_nulls_value(n) != (hash & (n_buckets - 1))))
goto again;
@@ -793,7 +877,7 @@ static void *__htab_map_lookup_elem(struct bpf_map *map, void *key)
head = select_bucket(htab, hash);
- 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);
return l;
}
@@ -919,7 +1003,7 @@ static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node)
int ret;
tgt_l = container_of(node, struct htab_elem_lru, lru_node);
- b = __select_bucket(htab, tgt_l->elem.hash);
+ b = __select_bucket(htab, READ_ONCE(tgt_l->hash));
head = &b->head;
ret = htab_lock_bucket(b, &flags);
@@ -961,7 +1045,7 @@ 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;
@@ -1004,7 +1088,7 @@ static void htab_elem_free(struct bpf_htab *htab, struct htab_elem *l)
check_and_cancel_fields(htab, l);
if (htab->map.map_type == BPF_MAP_TYPE_PERCPU_HASH)
- bpf_mem_cache_free(&htab->pcpu_ma, htab_elem_get_ptr_to_pptr(l));
+ bpf_mem_cache_free(&htab->pcpu_ma, htab_elem_get_ptr_to_pptr(htab, l));
bpf_mem_cache_free(&htab->ma, htab_elem_container(htab, l));
}
@@ -1169,7 +1253,9 @@ static struct htab_elem *alloc_htab_elem(struct bpf_htab *htab, void *key,
l_new = container + htab->elem_offset;
}
- memcpy(l_new->key, key, key_size);
+ if (htab_has_hash(htab))
+ memcpy(l_new->key, key, key_size);
+
if (percpu) {
if (prealloc) {
pptr = htab_elem_get_ptr(l_new, key_size);
@@ -1182,7 +1268,7 @@ static struct htab_elem *alloc_htab_elem(struct bpf_htab *htab, void *key,
l_new = ERR_PTR(-ENOMEM);
goto dec_count;
}
- htab_elem_set_ptr_to_pptr(l_new, ptr);
+ htab_elem_set_ptr_to_pptr(htab, l_new, ptr);
pptr = *(void __percpu **)ptr;
}
@@ -1201,7 +1287,26 @@ static struct htab_elem *alloc_htab_elem(struct bpf_htab *htab, void *key,
copy_map_value(&htab->map, htab_elem_value(l_new, key_size), value);
}
- l_new->hash = hash;
+ /*
+ * Order value/pptr initialization before publishing the new hash
+ * (or the new key when hash is omitted) so lockless RCU readers
+ * matching the new element never observe uninitialized data or
+ * a stale pptr.
+ * Pairs with smp_rmb() in lookup_nulls_elem_raw().
+ */
+ smp_wmb();
+ if (htab_has_hash(htab)) {
+ htab_elem_set_hash(htab, l_new, hash);
+ } else {
+ /*
+ * Zero-extend key into k for an atomic write to support
+ * lockless RCU readers.
+ */
+ unsigned long k = 0;
+
+ memcpy(&k, key, key_size);
+ WRITE_ONCE(*(unsigned long *)l_new->key, k);
+ }
return l_new;
dec_count:
dec_elem_count(htab);
@@ -1251,7 +1356,7 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
if (unlikely(!btf_record_has_field(map->record, BPF_SPIN_LOCK)))
return -EINVAL;
/* find an element without taking the bucket lock */
- l_old = lookup_nulls_elem_raw(head, hash, key, key_size,
+ l_old = lookup_nulls_elem_raw(htab, head, hash, key, key_size,
htab->n_buckets);
ret = check_flags(htab, l_old, map_flags);
if (ret)
@@ -1273,7 +1378,7 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
if (ret)
return ret;
- l_old = lookup_elem_raw(head, hash, key, key_size);
+ l_old = lookup_elem_raw(htab, head, hash, key, key_size);
ret = check_flags(htab, l_old, map_flags);
if (ret)
@@ -1371,7 +1476,7 @@ static long htab_lru_map_update_elem(struct bpf_map *map, void *key, void *value
if (ret)
goto err_lock_bucket;
- l_old = lookup_elem_raw(head, hash, key, key_size);
+ l_old = lookup_elem_raw(htab, head, hash, key, key_size);
ret = check_flags(htab, l_old, map_flags);
if (ret)
@@ -1438,7 +1543,7 @@ static long htab_map_update_elem_in_place(struct bpf_map *map, void *key,
if (ret)
return ret;
- l_old = lookup_elem_raw(head, hash, key, key_size);
+ l_old = lookup_elem_raw(htab, head, hash, key, key_size);
ret = check_flags(htab, l_old, map_flags);
if (ret)
@@ -1512,7 +1617,7 @@ static long __htab_lru_percpu_map_update_elem(struct bpf_map *map, void *key,
if (ret)
goto err_lock_bucket;
- l_old = lookup_elem_raw(head, hash, key, key_size);
+ l_old = lookup_elem_raw(htab, head, hash, key, key_size);
ret = check_flags(htab, l_old, map_flags);
if (ret)
@@ -1580,7 +1685,7 @@ static long htab_map_delete_elem(struct bpf_map *map, void *key)
if (ret)
return ret;
- l = lookup_elem_raw(head, hash, key, key_size);
+ l = lookup_elem_raw(htab, head, hash, key, key_size);
if (l)
hlist_nulls_del_rcu(&l->hash_node);
else
@@ -1615,7 +1720,7 @@ static long htab_lru_map_delete_elem(struct bpf_map *map, void *key)
if (ret)
return ret;
- l = lookup_elem_raw(head, hash, key, key_size);
+ l = lookup_elem_raw(htab, head, hash, key, key_size);
if (l)
hlist_nulls_del_rcu(&l->hash_node);
@@ -1756,7 +1861,7 @@ static int __htab_map_lookup_and_delete_elem(struct bpf_map *map, void *key,
if (ret)
return ret;
- l = lookup_elem_raw(head, hash, key, key_size);
+ l = lookup_elem_raw(htab, head, hash, key, key_size);
if (!l) {
ret = -ENOENT;
goto out_unlock;
@@ -2930,18 +3035,6 @@ static int rhtab_map_alloc_check(union bpf_attr *attr)
return htab_map_alloc_check(attr);
}
-static void rhtab_mem_dtor(void *obj, void *ctx)
-{
- struct htab_btf_record *hrec = ctx;
- struct rhtab_elem *elem = obj;
-
- if (IS_ERR_OR_NULL(hrec->record))
- return;
-
- bpf_obj_free_fields(hrec->record,
- rhtab_elem_value(elem, hrec->key_size));
-}
-
static void rhtab_free_elem(void *ptr, void *arg)
{
struct bpf_rhtab *rhtab = arg;
@@ -3167,7 +3260,9 @@ static int rhtab_map_check_btf(struct bpf_map *map, const struct btf *btf,
if (btf_type_is_void(key_type))
return -EINVAL;
- return bpf_ma_set_dtor(map, &rhtab->ma, rhtab_mem_dtor);
+ return bpf_ma_set_dtor(map, &rhtab->ma, htab_mem_dtor,
+ offsetof(struct rhtab_elem, data) +
+ round_up(map->key_size, 8));
}
static void rhtab_map_free_internal_structs(struct bpf_map *map)
diff --git a/tools/testing/selftests/bpf/progs/map_ptr_kern.c b/tools/testing/selftests/bpf/progs/map_ptr_kern.c
index f71be4fc8dd7..6bd4cb68c20c 100644
--- a/tools/testing/selftests/bpf/progs/map_ptr_kern.c
+++ b/tools/testing/selftests/bpf/progs/map_ptr_kern.c
@@ -114,7 +114,7 @@ static inline int check_hash(void)
VERIFY(check_default_noinline(&hash->map, map));
VERIFY(hash->n_buckets == MAX_ENTRIES);
- VERIFY(hash->elem_size == 40);
+ VERIFY(hash->elem_size == 32);
VERIFY(hash->count.counter == 0);
VERIFY(bpf_map_sum_elem_count(map) == 0);
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next v5 1/2] bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem
2026-09-21 0:22 ` [PATCH bpf-next v5 1/2] bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem T.J. Mercier
@ 2026-09-21 0:51 ` Alexei Starovoitov
0 siblings, 0 replies; 5+ messages in thread
From: Alexei Starovoitov @ 2026-09-21 0:51 UTC (permalink / raw)
To: T.J. Mercier, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, mykyta.yatsenko5
Cc: bpf, linux-kernel
On Sun, Sep 20, 2026 at 05:22 PM T.J. Mercier <tjmercier@google.com> wrote:
> - if ((u64)attr->key_size + attr->value_size >= KMALLOC_MAX_SIZE -
> - sizeof(struct htab_elem))
> + if (round_up((u64)attr->key_size, 8) + round_up((u64)attr->value_size, 8) >=
> + KMALLOC_MAX_SIZE - sizeof(union htab_elem_all))
sizeof(union htab_elem_all) is the same 48 bytes as before.
The round_up() part is an unrelated change in what map_create accepts.
Drop it from this patch.
[...]
> + if (htab_is_lru(htab))
> + elem_offset = offsetof(struct htab_elem_lru, elem);
> + else if (percpu && !prealloc)
> + elem_offset = offsetof(struct htab_elem_pcpu, elem);
> +
> + BUILD_BUG_ON(elem_offset + sizeof(struct htab_elem) >
> + sizeof(union htab_elem_all));
elem_offset is a run-time value. BUILD_BUG_ON() works here only when
the compiler manages to fold it, and it cannot trigger by construction
of the union. Drop it.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next v5 2/2] bpf: htab: Reduce elem_size by 8 bytes for small key sizes
2026-09-21 0:22 ` [PATCH bpf-next v5 2/2] bpf: htab: Reduce elem_size by 8 bytes for small key sizes T.J. Mercier
@ 2026-09-21 0:57 ` Alexei Starovoitov
0 siblings, 0 replies; 5+ messages in thread
From: Alexei Starovoitov @ 2026-09-21 0:57 UTC (permalink / raw)
To: T.J. Mercier, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, mykyta.yatsenko5
Cc: bpf, linux-kernel
On Sun, Sep 20, 2026 at 05:22 PM T.J. Mercier <tjmercier@google.com> wrote:
> + l = __lookup_elem_raw(htab, head, hash, key, key_size, &n);
> + if (l) {
> + /*
> + * Pairs with smp_wmb() in alloc_htab_elem() to ensure
> + * value/pptr reads happen after key/hash match on
> + * recycled elements.
> + */
> + smp_rmb();
> + return l;
> + }
Is this AI suggestion to add barriers?
I think it should be on the program side.
If users really care then they will add such barriers.
Doing it unconditionally will cost performance for everyone.
Especially on arm64.
If you disagree, please provide ./bench bpf-hashmap-lookup numbers on arm64
to demonstrate that perf is the same.
pw-bot: cr
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-21 0:57 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21 0:22 [PATCH bpf-next v5 0/2] bpf: htab: Reduce memory use of hash maps T.J. Mercier
2026-09-21 0:22 ` [PATCH bpf-next v5 1/2] bpf: htab: Split htab_elem_lru and htab_elem_pcpu off of htab_elem T.J. Mercier
2026-09-21 0:51 ` Alexei Starovoitov
2026-09-21 0:22 ` [PATCH bpf-next v5 2/2] bpf: htab: Reduce elem_size by 8 bytes for small key sizes T.J. Mercier
2026-09-21 0:57 ` Alexei Starovoitov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®