BPF List
 help / color / mirror / Atom feed
From: Emil Tsalapatis <emil@etsalapatis.com>
To: bpf@vger.kernel.org
Cc: ast@kernel.org, andrii@kernel.org, memxor@gmail.com,
	daniel@iogearbox.net, eddyz87@gmail.com,
	Emil Tsalapatis <emil@etsalapatis.com>
Subject: [PATCH 5/6] selftests/bpf: libarena: Add a benchmark for malloc()/calloc()
Date: Mon, 17 Aug 2026 15:16:15 -0400	[thread overview]
Message-ID: <20260817191616.11071-6-emil@etsalapatis.com> (raw)
In-Reply-To: <20260817191616.11071-1-emil@etsalapatis.com>

Add a benchmark for measuring the performance of the malloc()/calloc()
arena allocator calls. This is useful as a basic allocator performance
check that we can easily expand later. As with the regular arena
allocator, focus on sub-page allocations that cannot be satisfied
efficiently with the BPF arena page allocation code.

Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
---
 tools/testing/selftests/bpf/Makefile          |  18 +-
 tools/testing/selftests/bpf/bench.c           |   6 +
 .../selftests/bpf/benchs/bench_libarena.c     | 210 ++++++++++++++++++
 .../bpf/benchs/run_bench_libarena.sh          |  31 +++
 tools/testing/selftests/bpf/libarena/Makefile |  30 ++-
 .../bpf/libarena/benchs/bench_malloc.bpf.c    |  51 +++++
 6 files changed, 334 insertions(+), 12 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/benchs/bench_libarena.c
 create mode 100755 tools/testing/selftests/bpf/benchs/run_bench_libarena.sh
 create mode 100644 tools/testing/selftests/bpf/libarena/benchs/bench_malloc.bpf.c

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index d3655a706482..22e9eb16c151 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -826,18 +826,22 @@ LIBARENA_MAKE_ARGS = \
 		BPF_TARGET_ENDIAN="$(BPF_TARGET_ENDIAN)" \
 		Q="$(Q)"
 
-LIBARENA_BPF_DEPS := $(wildcard libarena/Makefile		\
-				 libarena/include/*		\
-				 libarena/include/libarena/*	\
-				 libarena/src/*			\
-				 libarena/selftests/*		\
-				 libarena/*.bpf.o)
+LIBARENA_COMMON_DEPS := $(wildcard libarena/Makefile		\
+				    libarena/include/*		\
+				    libarena/include/libarena/*	\
+				    libarena/src/*)
+LIBARENA_BPF_DEPS := $(LIBARENA_COMMON_DEPS) $(wildcard libarena/selftests/*)
+LIBARENA_BENCH_BPF_DEPS := $(LIBARENA_COMMON_DEPS) $(wildcard libarena/benchs/*)
 
 LIBARENA_SKEL := libarena/libarena.skel.h
+LIBARENA_BENCH_SKEL := libarena/libarena_bench.skel.h
 
 $(LIBARENA_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)
 	+$(MAKE) -C libarena libarena.skel.h $(LIBARENA_MAKE_ARGS)
 
+$(LIBARENA_BENCH_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BENCH_BPF_DEPS) | $(LIBARENA_SKEL)
+	+$(MAKE) -C libarena benchmarks $(LIBARENA_MAKE_ARGS)
+
 ifneq ($(CLANG_HAS_ARENA_ASAN),)
 LIBARENA_ASAN_SKEL := libarena/libarena_asan.skel.h
 CFLAGS += -DHAS_BPF_ARENA_ASAN
@@ -987,6 +991,7 @@ $(OUTPUT)/bench_sockmap.o: $(OUTPUT)/bench_sockmap_prog.skel.h
 $(OUTPUT)/bench_lpm_trie_map.o: $(OUTPUT)/lpm_trie_bench.skel.h $(OUTPUT)/lpm_trie_map.skel.h
 $(OUTPUT)/bench_bpf_nop.o: $(OUTPUT)/bpf_nop_bench.skel.h bench_bpf_timing.h
 $(OUTPUT)/bench_xdp_lb.o: $(OUTPUT)/xdp_lb_bench.skel.h bench_bpf_timing.h
+$(OUTPUT)/bench_libarena.o: $(LIBARENA_BENCH_SKEL)
 $(OUTPUT)/bench_bpf_timing.o: bench_bpf_timing.h
 $(OUTPUT)/bench.o: bench.h testing_helpers.h $(BPFOBJ)
 $(OUTPUT)/bench: LDLIBS += -lm
@@ -1014,6 +1019,7 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \
 		 $(OUTPUT)/bench_bpf_timing.o \
 		 $(OUTPUT)/bench_bpf_nop.o \
 		 $(OUTPUT)/bench_xdp_lb.o \
+		 $(OUTPUT)/bench_libarena.o \
 		 $(OUTPUT)/usdt_1.o \
 		 $(OUTPUT)/usdt_2.o \
 		 #
diff --git a/tools/testing/selftests/bpf/bench.c b/tools/testing/selftests/bpf/bench.c
index b86b73456d3c..de672f61d4ab 100644
--- a/tools/testing/selftests/bpf/bench.c
+++ b/tools/testing/selftests/bpf/bench.c
@@ -288,6 +288,7 @@ extern struct argp bench_crypto_argp;
 extern struct argp bench_sockmap_argp;
 extern struct argp bench_lpm_trie_map_argp;
 extern struct argp bench_xdp_lb_argp;
+extern struct argp bench_libarena_argp;
 
 static const struct argp_child bench_parsers[] = {
 	{ &bench_ringbufs_argp, 0, "Ring buffers benchmark", 0 },
@@ -306,6 +307,7 @@ static const struct argp_child bench_parsers[] = {
 	{ &bench_sockmap_argp, 0, "bpf sockmap benchmark", 0 },
 	{ &bench_lpm_trie_map_argp, 0, "LPM trie map benchmark", 0 },
 	{ &bench_xdp_lb_argp, 0, "XDP load-balancer benchmark", 0 },
+	{ &bench_libarena_argp, 0, "libarena allocator benchmark", 0 },
 	{},
 };
 
@@ -585,6 +587,8 @@ extern const struct bench bench_lpm_trie_delete;
 extern const struct bench bench_lpm_trie_free;
 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;
 
 static const struct bench *benchs[] = {
 	&bench_count_global,
@@ -669,6 +673,8 @@ static const struct bench *benchs[] = {
 	&bench_lpm_trie_free,
 	&bench_bpf_nop,
 	&bench_xdp_lb,
+	&bench_libarena_malloc,
+	&bench_libarena_calloc,
 };
 
 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
new file mode 100644
index 000000000000..24e432244bf5
--- /dev/null
+++ b/tools/testing/selftests/bpf/benchs/bench_libarena.c
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <argp.h>
+#include <limits.h>
+#include <string.h>
+
+#include "bench.h"
+
+#include <libarena/common.h>
+#include <libarena/asan.h>
+#include <libarena/buddy.h>
+#include <libarena/userspace.h>
+
+#include "libarena/libarena_bench.skel.h"
+
+static struct {
+	__u64 alloc_size;
+	__u64 nallocs;
+} args = {
+	.alloc_size = 64,
+	.nallocs = 10000,
+};
+
+static struct {
+	struct libarena_bench *skel;
+	int bench_fd;
+	int reset_fd;
+} ctx;
+
+enum {
+	ARG_LIBARENA_ALLOC_SIZE = 12000,
+	ARG_LIBARENA_NALLOCS,
+};
+
+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" },
+	{},
+};
+
+static error_t parse_arg(int key, char *arg, struct argp_state *state)
+{
+	unsigned long value;
+
+	switch (key) {
+	case ARG_LIBARENA_ALLOC_SIZE:
+		value = strtoull(arg, NULL, 10);
+		if (!value || value >= UINT_MAX) {
+			fprintf(stderr, "invalid alloc_size: %ld", value);
+			argp_usage(state);
+		}
+		args.alloc_size = value;
+		break;
+	case ARG_LIBARENA_NALLOCS:
+		args.nallocs = strtoull(arg, NULL, 10);
+		break;
+	default:
+		return ARGP_ERR_UNKNOWN;
+	}
+
+	return 0;
+}
+
+const struct argp bench_libarena_argp = {
+	.options = opts,
+	.parser = parse_arg,
+};
+
+static void validate(void)
+{
+	if (env.consumer_cnt != 0) {
+		fprintf(stderr, "benchmark doesn't support consumers\n");
+		exit(1);
+	}
+
+	if (env.producer_cnt != 1) {
+		fprintf(stderr, "benchmark supports exactly one producer\n");
+		exit(1);
+	}
+}
+
+static void setup_common(void)
+{
+	struct arena_alloc_reserve_args reserve_args = {
+		.nr_pages = ARENA_RESERVE_PAGES_DFL,
+	};
+	int err;
+
+	setup_libbpf();
+
+	ctx.skel = libarena_bench__open_and_load();
+	if (!ctx.skel) {
+		fprintf(stderr, "failed to open and load skeleton\n");
+		exit(1);
+	}
+
+	err = libarena_run_prog_args(
+		bpf_program__fd(ctx.skel->progs.arena_alloc_reserve),
+		&reserve_args, sizeof(reserve_args));
+	if (err) {
+		fprintf(stderr, "failed to reserve arena pages: %d\n", err);
+		exit(1);
+	}
+
+	err = libarena_run_prog(
+		bpf_program__fd(ctx.skel->progs.arena_buddy_reset));
+	if (err) {
+		fprintf(stderr, "failed to initialize arena allocator: %d\n", err);
+		exit(1);
+	}
+
+	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);
+}
+
+static void malloc_setup(void)
+{
+	setup_common();
+	ctx.bench_fd = bpf_program__fd(ctx.skel->progs.bench_malloc);
+}
+
+static void calloc_setup(void)
+{
+	setup_common();
+	ctx.bench_fd = bpf_program__fd(ctx.skel->progs.bench_calloc);
+}
+
+static void *producer(void *input)
+{
+	int err;
+
+	while (true) {
+		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);
+		}
+	}
+
+	return NULL;
+}
+
+static void measure(struct bench_res *res)
+{
+	res->duration_ns = atomic_swap(&ctx.skel->bss->bench_duration_ns, 0);
+	res->hits = atomic_swap(&ctx.skel->bss->bench_hits, 0);
+}
+
+static void report_progress(int iter, struct bench_res *res, long delta_ns)
+{
+	double latency_ns = 0.0;
+
+	if (res->hits)
+		latency_ns = res->duration_ns / (double)res->hits;
+
+	printf("Iter %3d (%7.3lfus): latency %8.3lf ns/op (%ld allocations)\n",
+	       iter, (delta_ns - 1000000000) / 1000.0, latency_ns, res->hits);
+}
+
+static void report_final(struct bench_res res[], int res_cnt)
+{
+	unsigned long duration_ns = 0;
+	long hits = 0;
+	int i;
+
+	for (i = 0; i < res_cnt; i++) {
+		duration_ns += res[i].duration_ns;
+		hits += res[i].hits;
+	}
+
+	if (!hits || !res_cnt) {
+		printf("Summary: no runs measured\n");
+		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);
+}
+
+const struct bench bench_libarena_malloc = {
+	.name = "libarena-malloc",
+	.argp = &bench_libarena_argp,
+	.validate = validate,
+	.setup = malloc_setup,
+	.producer_thread = producer,
+	.measure = measure,
+	.report_progress = report_progress,
+	.report_final = report_final,
+};
+
+const struct bench bench_libarena_calloc = {
+	.name = "libarena-calloc",
+	.argp = &bench_libarena_argp,
+	.validate = validate,
+	.setup = calloc_setup,
+	.producer_thread = producer,
+	.measure = measure,
+	.report_progress = report_progress,
+	.report_final = report_final,
+};
diff --git a/tools/testing/selftests/bpf/benchs/run_bench_libarena.sh b/tools/testing/selftests/bpf/benchs/run_bench_libarena.sh
new file mode 100755
index 000000000000..10afe4d52ebf
--- /dev/null
+++ b/tools/testing/selftests/bpf/benchs/run_bench_libarena.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+source ./benchs/run_common.sh
+
+set -eufo pipefail
+
+RUN_BENCH="./bench -d3 -q"
+
+summarize_libarena()
+{
+	local bench="$1"
+	local summary
+
+	summary=$(printf '%s\n' "$2" | tail -n1)
+	summary=${summary#Summary: }
+	printf "%-20s %s\n" "$bench" "$summary"
+}
+
+header "libarena sequential malloc\n"
+
+for size in 16 64 256 1024 4096; do
+subtitle "allocation size: $size"
+printf "\t-------------------\n"
+	for nallocs in 10 50 100 500 1000 5000 10000; do
+		summarize_libarena "malloc:" \
+			"$($RUN_BENCH --alloc_size "$size" --nallocs "$nallocs" libarena-malloc)"
+		summarize_libarena "calloc:" \
+			"$($RUN_BENCH --alloc_size "$size" --nallocs "$nallocs" libarena-calloc)"
+	done
+done
diff --git a/tools/testing/selftests/bpf/libarena/Makefile b/tools/testing/selftests/bpf/libarena/Makefile
index 5e2ab514805e..6d62eda34920 100644
--- a/tools/testing/selftests/bpf/libarena/Makefile
+++ b/tools/testing/selftests/bpf/libarena/Makefile
@@ -27,10 +27,17 @@ BPFDIR=$(abspath $(LIBARENA)/..)
 INCLUDE_DIR ?= $(BPFDIR)/tools/include
 LIBBPF_INCLUDE ?= $(INCLUDE_DIR)
 
-# Scan src/ and selftests/ to generate the final binaries
-LIBARENA_SOURCES = $(wildcard $(LIBARENA)/src/*.bpf.c) $(wildcard $(LIBARENA)/selftests/*.bpf.c)
-LIBARENA_OBJECTS = $(notdir $(LIBARENA_SOURCES:.bpf.c=.bpf.o))
-LIBARENA_OBJECTS_ASAN = $(notdir $(LIBARENA_SOURCES:.bpf.c=_asan.bpf.o))
+# Build selftests and benchmarks into separate BPF objects and skeletons.
+LIBARENA_CORE_SOURCES = $(wildcard $(LIBARENA)/src/*.bpf.c)
+LIBARENA_TEST_SOURCES = $(wildcard $(LIBARENA)/selftests/*.bpf.c)
+LIBARENA_BENCH_SOURCES = $(wildcard $(LIBARENA)/benchs/*.bpf.c)
+
+LIBARENA_OBJECTS = $(notdir $(LIBARENA_CORE_SOURCES:.bpf.c=.bpf.o) \
+			       $(LIBARENA_TEST_SOURCES:.bpf.c=.bpf.o))
+LIBARENA_OBJECTS_ASAN = $(notdir $(LIBARENA_CORE_SOURCES:.bpf.c=_asan.bpf.o) \
+				    $(LIBARENA_TEST_SOURCES:.bpf.c=_asan.bpf.o))
+LIBARENA_BENCH_OBJECTS = $(notdir $(LIBARENA_CORE_SOURCES:.bpf.c=.bpf.o) \
+				     $(LIBARENA_BENCH_SOURCES:.bpf.c=.bpf.o))
 
 INCLUDES = -I$(LIBARENA)/include -I$(BPFDIR)
 ifneq ($(INCLUDE_DIR),)
@@ -58,12 +65,19 @@ override BPF_CFLAGS += $(INCLUDES)
 CFLAGS = -O2 -no-pie
 CFLAGS += $(INCLUDES)
 
-vpath %.bpf.c $(LIBARENA)/src $(LIBARENA)/selftests
-vpath %.c $(LIBARENA)/src $(LIBARENA)/selftests
+vpath %.bpf.c $(LIBARENA)/src $(LIBARENA)/selftests $(LIBARENA)/benchs
+vpath %.c $(LIBARENA)/src $(LIBARENA)/selftests $(LIBARENA)/benchs
 
 skeletons: libarena.skel.h libarena_asan.skel.h
 .PHONY: skeletons
 
+benchmarks: libarena_bench.skel.h
+.PHONY: benchmarks
+
+libarena_bench.skel.h: libarena_bench.bpf.o
+	$(call msg,GEN-SKEL,libarena,$@)
+	$(Q)$(BPFTOOL) gen skeleton $< name "libarena_bench" > $@
+
 libarena_asan.skel.h: libarena_asan.bpf.o
 	$(call msg,GEN-SKEL,libarena,$@)
 	$(Q)$(BPFTOOL) gen skeleton $< name "libarena_asan" > $@
@@ -80,6 +94,10 @@ libarena.bpf.o: $(LIBARENA_OBJECTS)
 	$(call msg,GEN-OBJ,libarena,$@)
 	$(Q)$(BPFTOOL) gen object $@ $^
 
+libarena_bench.bpf.o: $(LIBARENA_BENCH_OBJECTS)
+	$(call msg,GEN-OBJ,libarena,$@)
+	$(Q)$(BPFTOOL) gen object $@ $^
+
 %_asan.bpf.o: %.bpf.c
 	$(call msg,CLNG-BPF,libarena,$@)
 	$(Q)$(CLANG) $(BPF_CFLAGS) $(ASAN_FLAGS) -DBPF_ARENA_ASAN $(BPF_TARGET_ENDIAN) -c $< -o $@
diff --git a/tools/testing/selftests/bpf/libarena/benchs/bench_malloc.bpf.c b/tools/testing/selftests/bpf/libarena/benchs/bench_malloc.bpf.c
new file mode 100644
index 000000000000..e06397dc9a40
--- /dev/null
+++ b/tools/testing/selftests/bpf/libarena/benchs/bench_malloc.bpf.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <libarena/common.h>
+
+#include <libarena/asan.h>
+#include <libarena/buddy.h>
+
+u32 bench_alloc_size;
+u32 bench_nallocs;
+long bench_hits;
+long bench_duration_ns;
+
+SEC("syscall")
+int bench_malloc(void)
+{
+	void __arena *mem;
+	u64 start_ns;
+	u32 i;
+
+	start_ns = bpf_ktime_get_ns();
+	for (i = zero; i < bench_nallocs && can_loop; i++) {
+		mem = arena_malloc(bench_alloc_size);
+		if (!mem)
+			return -ENOMEM;
+	}
+
+	__sync_add_and_fetch(&bench_duration_ns,
+			     bpf_ktime_get_ns() - start_ns);
+	__sync_add_and_fetch(&bench_hits, i);
+	return 0;
+}
+
+SEC("syscall")
+int bench_calloc(void)
+{
+	void __arena *mem;
+	u64 start_ns;
+	u32 i;
+
+	start_ns = bpf_ktime_get_ns();
+	for (i = zero; i < bench_nallocs && can_loop; i++) {
+		mem = arena_calloc(1, bench_alloc_size);
+		if (!mem)
+			return -ENOMEM;
+	}
+
+	__sync_add_and_fetch(&bench_duration_ns,
+			     bpf_ktime_get_ns() - start_ns);
+	__sync_add_and_fetch(&bench_hits, i);
+	return 0;
+}
-- 
2.54.0


  parent reply	other threads:[~2026-08-17 19:16 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-17 19:16 [PATCH 0/6] selftests/bpf: Fixes and improvements for libarena Emil Tsalapatis
2026-08-17 19:16 ` [PATCH 1/6] selftests/bpf: libarena: Normalize SPDX headers across files Emil Tsalapatis
2026-08-17 19:21   ` sashiko-bot
2026-08-17 19:16 ` [PATCH 2/6] selftests/bpf: libarena: Inline nonatomic bitmap operations Emil Tsalapatis
2026-08-17 19:26   ` sashiko-bot
2026-08-17 20:25   ` bot+bpf-ci
2026-08-17 19:16 ` [PATCH 3/6] selftests/bpf: libarena: Disable IRQs during allocation Emil Tsalapatis
2026-08-17 19:28   ` sashiko-bot
2026-08-17 20:38   ` bot+bpf-ci
2026-08-17 19:16 ` [PATCH 4/6] selftests/bpf: libarena: Add calloc() call Emil Tsalapatis
2026-08-17 19:23   ` sashiko-bot
2026-08-17 20:25   ` bot+bpf-ci
2026-08-17 19:16 ` Emil Tsalapatis [this message]
2026-08-17 19:31   ` [PATCH 5/6] selftests/bpf: libarena: Add a benchmark for malloc()/calloc() sashiko-bot
2026-08-17 20:25   ` bot+bpf-ci
2026-08-17 19:16 ` [PATCH 6/6] selftests/bpf: libarena: Optimize and make public arena_memset Emil Tsalapatis
2026-08-17 20:25   ` bot+bpf-ci

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