From: Emil Tsalapatis <emil@etsalapatis.com>
To: bpf@vger.kernel.org
Cc: ast@kernel.org, andrii@kernel.org, eddyz87@gmail.com,
memxor@gmail.com, daniel@iogearbox.net,
Emil Tsalapatis <emil@etsalapatis.com>
Subject: [PATCH bpf-next v1 2/7] selftests/bpf: Add a parallel buddy allocator benchmark
Date: Tue, 20 Oct 2026 18:18:55 +0000 [thread overview]
Message-ID: <20261020181900.6755-3-emil@etsalapatis.com> (raw)
In-Reply-To: <20261020181900.6755-1-emil@etsalapatis.com>
Add a benchmark for measuring buddy allocator latency under concurrent
load. Each worker performs a configurable number of allocations followed
by the corresponding frees.
Vary the worker count, allocation count and allocation size to expose both
lock contention and cache pressure. Add the benchmark to the libarena
runner alongside the existing single-threaded cases.
Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
---
tools/testing/selftests/bpf/bench.c | 2 +
.../selftests/bpf/benchs/bench_libarena.c | 100 ++++++++++++++++--
.../bpf/benchs/run_bench_libarena.sh | 8 ++
.../bpf/libarena/benchs/bench_malloc.bpf.c | 43 ++++++++
4 files changed, 142 insertions(+), 11 deletions(-)
diff --git a/tools/testing/selftests/bpf/bench.c b/tools/testing/selftests/bpf/bench.c
index d5f494184f9d..7d2a3587971f 100644
--- a/tools/testing/selftests/bpf/bench.c
+++ b/tools/testing/selftests/bpf/bench.c
@@ -589,6 +589,7 @@ extern const struct bench bench_bpf_nop;
extern const struct bench bench_xdp_lb;
extern const struct bench bench_libarena_malloc;
extern const struct bench bench_libarena_calloc;
+extern const struct bench bench_libarena_malloc_free;
static const struct bench *benchs[] = {
&bench_count_global,
@@ -675,6 +676,7 @@ static const struct bench *benchs[] = {
&bench_xdp_lb,
&bench_libarena_malloc,
&bench_libarena_calloc,
+ &bench_libarena_malloc_free,
};
static void find_benchmark(void)
diff --git a/tools/testing/selftests/bpf/benchs/bench_libarena.c b/tools/testing/selftests/bpf/benchs/bench_libarena.c
index 24e432244bf5..1185a471123c 100644
--- a/tools/testing/selftests/bpf/benchs/bench_libarena.c
+++ b/tools/testing/selftests/bpf/benchs/bench_libarena.c
@@ -25,6 +25,10 @@ static struct {
struct libarena_bench *skel;
int bench_fd;
int reset_fd;
+ bool reset_each_run;
+ bool stop;
+ int stopped;
+ int samples;
} ctx;
enum {
@@ -36,7 +40,7 @@ static const struct argp_option opts[] = {
{ "alloc_size", ARG_LIBARENA_ALLOC_SIZE, "BYTES", 0,
"Size of each arena allocation" },
{ "nallocs", ARG_LIBARENA_NALLOCS, "ITERS", 0,
- "Number of allocation per measurement" },
+ "Number of allocations per invocation" },
{},
};
@@ -68,12 +72,17 @@ const struct argp bench_libarena_argp = {
.parser = parse_arg,
};
-static void validate(void)
+static void validate_common(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumers\n");
exit(1);
}
+}
+
+static void validate(void)
+{
+ validate_common();
if (env.producer_cnt != 1) {
fprintf(stderr, "benchmark supports exactly one producer\n");
@@ -81,6 +90,21 @@ static void validate(void)
}
}
+static void malloc_free_validate(void)
+{
+ validate_common();
+
+ if (env.producer_cnt < 1) {
+ fprintf(stderr, "benchmark requires at least one producer\n");
+ exit(1);
+ }
+
+ if (args.alloc_size < sizeof(void *)) {
+ fprintf(stderr, "allocation size must hold a pointer\n");
+ exit(1);
+ }
+}
+
static void setup_common(void)
{
struct arena_alloc_reserve_args reserve_args = {
@@ -114,38 +138,55 @@ static void setup_common(void)
ctx.skel->bss->bench_alloc_size = args.alloc_size;
ctx.skel->bss->bench_nallocs = args.nallocs;
ctx.reset_fd = bpf_program__fd(ctx.skel->progs.arena_buddy_reset);
+ ctx.reset_each_run = false;
+ ctx.stop = false;
+ ctx.stopped = 0;
+ ctx.samples = 0;
}
static void malloc_setup(void)
{
setup_common();
ctx.bench_fd = bpf_program__fd(ctx.skel->progs.bench_malloc);
+ ctx.reset_each_run = true;
}
static void calloc_setup(void)
{
setup_common();
ctx.bench_fd = bpf_program__fd(ctx.skel->progs.bench_calloc);
+ ctx.reset_each_run = true;
+}
+
+static void malloc_free_setup(void)
+{
+ setup_common();
+ ctx.bench_fd = bpf_program__fd(ctx.skel->progs.bench_malloc_free);
+ ctx.skel->bss->bench_collect = false;
}
static void *producer(void *input)
{
int err;
- while (true) {
+ while (!__atomic_load_n(&ctx.stop, __ATOMIC_ACQUIRE)) {
err = libarena_run_prog(ctx.bench_fd);
if (err) {
fprintf(stderr, "libarena benchmark failed: %d\n", err);
exit(1);
}
- err = libarena_run_prog(ctx.reset_fd);
- if (err) {
- fprintf(stderr, "libarena alloc reset failed: %d\n", err);
- exit(1);
+ if (ctx.reset_each_run) {
+ err = libarena_run_prog(ctx.reset_fd);
+ if (err) {
+ fprintf(stderr, "libarena alloc reset failed: %d\n", err);
+ exit(1);
+ }
}
}
+ __atomic_add_fetch(&ctx.stopped, 1, __ATOMIC_RELEASE);
+
return NULL;
}
@@ -155,6 +196,15 @@ static void measure(struct bench_res *res)
res->hits = atomic_swap(&ctx.skel->bss->bench_hits, 0);
}
+static void malloc_free_measure(struct bench_res *res)
+{
+ (void)res;
+
+ if (++ctx.samples == env.warmup_sec)
+ __atomic_store_n(&ctx.skel->bss->bench_collect, true,
+ __ATOMIC_RELAXED);
+}
+
static void report_progress(int iter, struct bench_res *res, long delta_ns)
{
double latency_ns = 0.0;
@@ -162,7 +212,7 @@ static void report_progress(int iter, struct bench_res *res, long delta_ns)
if (res->hits)
latency_ns = res->duration_ns / (double)res->hits;
- printf("Iter %3d (%7.3lfus): latency %8.3lf ns/op (%ld allocations)\n",
+ printf("Iter %3d (%7.3lfus): latency %8.3lf ns/allocation (%ld allocations)\n",
iter, (delta_ns - 1000000000) / 1000.0, latency_ns, res->hits);
}
@@ -182,9 +232,27 @@ static void report_final(struct bench_res res[], int res_cnt)
return;
}
- printf("Summary: %.3lf ns/op, %.0lf invocations for %u allocations/invocation)\n",
- duration_ns / (double)hits, hits / (double)res_cnt,
- ctx.skel->bss->bench_nallocs);
+ printf("Summary: %.3lf ns/allocation (%ld allocations)\n",
+ duration_ns / (double)hits, hits);
+}
+
+static void malloc_free_report_final(struct bench_res res[], int res_cnt)
+{
+ struct bench_res final = {};
+
+ (void)res;
+ (void)res_cnt;
+
+ __atomic_store_n(&ctx.stop, true, __ATOMIC_RELEASE);
+ while (__atomic_load_n(&ctx.stopped, __ATOMIC_ACQUIRE) != env.producer_cnt)
+ usleep(1000);
+
+ final.duration_ns = __atomic_load_n(
+ &ctx.skel->bss->bench_duration_ns, __ATOMIC_RELAXED);
+ final.hits = __atomic_load_n(
+ &ctx.skel->bss->bench_hits, __ATOMIC_RELAXED);
+
+ report_final(&final, 1);
}
const struct bench bench_libarena_malloc = {
@@ -208,3 +276,13 @@ const struct bench bench_libarena_calloc = {
.report_progress = report_progress,
.report_final = report_final,
};
+
+const struct bench bench_libarena_malloc_free = {
+ .name = "libarena-malloc-free",
+ .argp = &bench_libarena_argp,
+ .validate = malloc_free_validate,
+ .setup = malloc_free_setup,
+ .producer_thread = producer,
+ .measure = malloc_free_measure,
+ .report_final = malloc_free_report_final,
+};
diff --git a/tools/testing/selftests/bpf/benchs/run_bench_libarena.sh b/tools/testing/selftests/bpf/benchs/run_bench_libarena.sh
index 10afe4d52ebf..5d5f84310b26 100755
--- a/tools/testing/selftests/bpf/benchs/run_bench_libarena.sh
+++ b/tools/testing/selftests/bpf/benchs/run_bench_libarena.sh
@@ -29,3 +29,11 @@ printf "\t-------------------\n"
"$($RUN_BENCH --alloc_size "$size" --nallocs "$nallocs" libarena-calloc)"
done
done
+
+header "libarena parallel malloc/free\n"
+
+for producers in 1 2 4 8; do
+ summarize_libarena "$producers producers:" \
+ "$($RUN_BENCH -a -p "$producers" --alloc_size 64 --nallocs 1000 \
+ libarena-malloc-free)"
+done
diff --git a/tools/testing/selftests/bpf/libarena/benchs/bench_malloc.bpf.c b/tools/testing/selftests/bpf/libarena/benchs/bench_malloc.bpf.c
index e06397dc9a40..3c9235c56b00 100644
--- a/tools/testing/selftests/bpf/libarena/benchs/bench_malloc.bpf.c
+++ b/tools/testing/selftests/bpf/libarena/benchs/bench_malloc.bpf.c
@@ -9,6 +9,7 @@ u32 bench_alloc_size;
u32 bench_nallocs;
long bench_hits;
long bench_duration_ns;
+bool bench_collect;
SEC("syscall")
int bench_malloc(void)
@@ -49,3 +50,45 @@ int bench_calloc(void)
__sync_add_and_fetch(&bench_hits, i);
return 0;
}
+
+struct bench_alloc {
+ struct bench_alloc __arena *next;
+};
+
+SEC("syscall")
+int bench_malloc_free(void)
+{
+ struct bench_alloc __arena *next, *obj, *head = NULL;
+ u64 duration_ns = 0, start_ns;
+ u32 i, nallocs;
+ bool collect;
+
+ collect = READ_ONCE(bench_collect);
+ for (i = zero; i < bench_nallocs && can_loop; i++) {
+ start_ns = bpf_ktime_get_ns();
+ obj = arena_malloc(bench_alloc_size);
+ duration_ns += bpf_ktime_get_ns() - start_ns;
+ if (!obj)
+ break;
+
+ obj->next = head;
+ head = obj;
+ }
+ nallocs = i;
+
+ for (i = zero; head && can_loop; i++) {
+ next = head->next;
+ arena_free(head);
+ head = next;
+ }
+
+ if (nallocs != bench_nallocs)
+ return -ENOMEM;
+
+ if (!collect)
+ return 0;
+
+ __sync_add_and_fetch(&bench_duration_ns, duration_ns);
+ __sync_add_and_fetch(&bench_hits, nallocs);
+ return 0;
+}
--
2.52.0
next prev parent reply other threads:[~2026-10-20 18:19 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 18:18 [PATCH bpf-next v1 0/7] bpf libarena allocator improvements Emil Tsalapatis
2026-10-20 18:18 ` [PATCH bpf-next v1 1/7] selftests/bpf: libarena: Add bulk buddy allocation helpers Emil Tsalapatis
2026-10-20 18:18 ` Emil Tsalapatis [this message]
2026-10-20 18:18 ` [PATCH bpf-next v1 3/7] selftests/bpf: libarena: Add buddy per-CPU caches Emil Tsalapatis
2026-10-20 18:18 ` [PATCH bpf-next v1 4/7] selftests/bpf: libarena: Handle NMI reentrancy Emil Tsalapatis
2026-10-20 18:18 ` [PATCH bpf-next v1 5/7] selftests/bpf: libarena: Refill local caches in batches Emil Tsalapatis
2026-10-20 18:18 ` [PATCH bpf-next v1 6/7] selftests/bpf: libarena: Add a transfer cache Emil Tsalapatis
2026-10-20 18:19 ` [PATCH bpf-next v1 7/7] selftests/bpf: libarena: Cover buddy cache fast paths Emil Tsalapatis
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=20261020181900.6755-3-emil@etsalapatis.com \
--to=emil@etsalapatis.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=memxor@gmail.com \
/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.