From: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>
To: "Puranjay Mohan" <puranjay@kernel.org>, <bpf@vger.kernel.org>
Cc: "Daniel Borkmann" <daniel@iogearbox.net>,
"Andrii Nakryiko" <andrii@kernel.org>,
"Martin KaFai Lau" <martin.lau@linux.dev>,
"Eduard Zingerman" <eddyz87@gmail.com>,
"Kumar Kartikeya Dwivedi" <memxor@gmail.com>,
"Song Liu" <song@kernel.org>,
"Yonghong Song" <yonghong.song@linux.dev>
Subject: Re: [PATCH bpf-next] bpf: Don't use percpu_counter for the htab element count in NMI
Date: Tue, 20 Oct 2026 14:53:49 +0000 [thread overview]
Message-ID: <DM9R2CURM0WN.2FT8Z1Y21S8MA@gmail.com> (raw)
In-Reply-To: <20261020131524.207415-1-puranjay@kernel.org>
On Tue, Oct 20, 2026 at 06:15 AM Puranjay Mohan <puranjay@kernel.org> wrote:
> Count the elements added or removed in NMI in htab->count, which is
> otherwise unused for such maps, and add it to the percpu_counter where
> that is read. In NMI is_map_full() only reads the percpu_counter, so the
> limit is approximate by up to PERCPU_COUNTER_BATCH * num_online_cpus()
> there.
Not only NMI. queued_spin_lock_slowpath() calls trace_contention_end()
with the lock already held. When percpu_counter_add_batch() contends on
fbc->lock, a tp_btf/contention_end prog that updates the same map sees
the same this_cpu counter, goes to the slow path too and spins on
fbc->lock that its own cpu holds. in_nmi() is false there.
can we drop percpu_counter from htab instead ?
map.elem_count is already a lockless per-cpu count of the same elements.
htab->count can follow it in steps of the batch and is_map_full() can
sum elem_count when it gets close to max_entries. That is exact in NMI
as well. Then no need for pcount, in_nmi() checks and adding up two
counters.
Something like this (compile tested only):
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 7750df9..b298b89 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3041,6 +3041,8 @@ static inline void bpf_map_dec_elem_count(struct bpf_map *map)
this_cpu_dec(*map->elem_count);
}
+s64 bpf_map_sum_elem_count(const struct bpf_map *map);
+
extern int sysctl_unprivileged_bpf_disabled;
bool bpf_token_capable(const struct bpf_token *token, int cap);
diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c
index bf78e09..6e21154 100644
--- a/kernel/bpf/hashtab.c
+++ b/kernel/bpf/hashtab.c
@@ -94,10 +94,10 @@ struct bpf_htab {
struct bpf_lru lru;
};
struct htab_elem *__percpu *extra_elems;
- /* number of elements in non-preallocated hashtable are kept
- * in either pcount or count
+ /* number of elements in non-preallocated hashtable. With
+ * use_percpu_counter it only follows map.elem_count in steps of
+ * PERCPU_COUNTER_BATCH, see is_map_full()
*/
- struct percpu_counter pcount;
atomic_t count;
bool use_percpu_counter;
bool has_hash;
@@ -739,29 +739,20 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
htab_init_buckets(htab);
-/* compute_batch_value() computes batch value as num_online_cpus() * 2
- * and __percpu_counter_compare() needs
- * htab->max_entries - cur_number_of_elems to be more than batch * num_online_cpus()
- * for percpu_counter to be faster than atomic_t. In practice the average bpf
- * hash map size is 10k, which means that a system with 64 cpus will fill
- * hashmap to 20% of 10k before percpu_counter becomes ineffective. Therefore
- * define our own batch count as 32 then 10k hash map can be filled up to 80%:
+/* is_map_full() needs htab->max_entries - cur_number_of_elems to be more than
+ * batch * nr_cpu_ids for the percpu counter to be faster than atomic_t. In
+ * practice the average bpf hash map size is 10k. With batch count of 32 a 10k
+ * hash map on a system with 64 cpus can be filled up to 80%:
* 10k - 8k > 32 _batch_ * 64 _cpus_
- * and __percpu_counter_compare() will still be fast. At that point hash map
+ * and is_map_full() will still be fast. At that point hash map
* collisions will dominate its performance anyway. Assume that hash map filled
* to 50+% isn't going to be O(1) and use the following formula to choose
- * between percpu_counter and atomic_t.
+ * between the percpu counter and atomic_t. The batch has to be a power of 2.
*/
#define PERCPU_COUNTER_BATCH 32
- if (attr->max_entries / 2 > num_online_cpus() * PERCPU_COUNTER_BATCH)
+ if (attr->max_entries / 2 > nr_cpu_ids * PERCPU_COUNTER_BATCH)
htab->use_percpu_counter = true;
- if (htab->use_percpu_counter) {
- err = percpu_counter_init(&htab->pcount, 0, GFP_KERNEL);
- if (err)
- goto free_map_locked;
- }
-
if (prealloc) {
err = prealloc_init(htab);
if (err)
@@ -789,8 +780,6 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
free_prealloc:
prealloc_destroy(htab);
free_map_locked:
- if (htab->use_percpu_counter)
- percpu_counter_destroy(&htab->pcount);
bpf_map_area_free(htab->buckets);
bpf_mem_alloc_destroy(&htab->pcpu_ma);
bpf_mem_alloc_destroy(&htab->ma);
@@ -1162,30 +1151,33 @@ static void htab_put_fd_value(struct bpf_htab *htab, struct htab_elem *l)
static bool is_map_full(struct bpf_htab *htab)
{
- if (htab->use_percpu_counter)
- return __percpu_counter_compare(&htab->pcount, htab->map.max_entries,
- PERCPU_COUNTER_BATCH) >= 0;
- return atomic_read(&htab->count) >= htab->map.max_entries;
+ s64 count = atomic_read(&htab->count);
+
+ /* each cpu's elem_count is ahead of count by less than the batch */
+ if (htab->use_percpu_counter &&
+ count + nr_cpu_ids * PERCPU_COUNTER_BATCH >= htab->map.max_entries)
+ count = bpf_map_sum_elem_count(&htab->map);
+ return count >= htab->map.max_entries;
}
static void inc_elem_count(struct bpf_htab *htab)
{
- bpf_map_inc_elem_count(&htab->map);
+ s64 cnt = this_cpu_inc_return(*htab->map.elem_count);
- if (htab->use_percpu_counter)
- percpu_counter_add_batch(&htab->pcount, 1, PERCPU_COUNTER_BATCH);
- else
+ if (!htab->use_percpu_counter)
atomic_inc(&htab->count);
+ else if (!(cnt & (PERCPU_COUNTER_BATCH - 1)))
+ atomic_add(PERCPU_COUNTER_BATCH, &htab->count);
}
static void dec_elem_count(struct bpf_htab *htab)
{
- bpf_map_dec_elem_count(&htab->map);
+ s64 cnt = this_cpu_dec_return(*htab->map.elem_count);
- if (htab->use_percpu_counter)
- percpu_counter_add_batch(&htab->pcount, -1, PERCPU_COUNTER_BATCH);
- else
+ if (!htab->use_percpu_counter)
atomic_dec(&htab->count);
+ else if (!((cnt + 1) & (PERCPU_COUNTER_BATCH - 1)))
+ atomic_sub(PERCPU_COUNTER_BATCH, &htab->count);
}
static void free_htab_elem(struct bpf_htab *htab, struct htab_elem *l)
@@ -1866,8 +1858,6 @@ static void htab_map_free(struct bpf_map *map)
bpf_map_area_free(htab->buckets);
bpf_mem_alloc_destroy(&htab->pcpu_ma);
bpf_mem_alloc_destroy(&htab->ma);
- if (htab->use_percpu_counter)
- percpu_counter_destroy(&htab->pcount);
bpf_map_area_free(htab);
}
@@ -2577,9 +2567,7 @@ static u64 htab_map_mem_usage(const struct bpf_map *map)
} else {
#define LLIST_NODE_SZ sizeof(struct llist_node)
- num_entries = htab->use_percpu_counter ?
- percpu_counter_sum(&htab->pcount) :
- atomic_read(&htab->count);
+ num_entries = bpf_map_sum_elem_count(map);
usage += (htab->elem_size + LLIST_NODE_SZ) * num_entries;
if (percpu) {
usage += (LLIST_NODE_SZ + sizeof(void *)) * num_entries;
pw-bot: cr
prev parent reply other threads:[~2026-10-20 14:53 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 13:15 [PATCH bpf-next] bpf: Don't use percpu_counter for the htab element count in NMI Puranjay Mohan
2026-10-20 13:28 ` sashiko-bot
2026-10-20 13:53 ` Mykyta Yatsenko
2026-10-20 14:53 ` Alexei Starovoitov [this message]
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=DM9R2CURM0WN.2FT8Z1Y21S8MA@gmail.com \
--to=alexei.starovoitov@gmail.com \
--cc=andrii@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=puranjay@kernel.org \
--cc=song@kernel.org \
--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