mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v7] kunit: memcpy: Add benchmark
@ 2026-11-10  2:16 Matteo Croce
  2026-11-10  2:20 ` Matteo Croce
  0 siblings, 1 reply; 2+ messages in thread
From: Matteo Croce @ 2026-11-10  2:16 UTC (permalink / raw)
  To: Brendan Higgins, David Gow, Andrew Morton, David Laight
  Cc: linux-kselftest, linux-kernel

Add optional benchmarks for memcpy() and memmove(), enabled with
CONFIG_MEMCPY_KUNIT_BENCHMARK.

Each function is run over several buffer sizes, up to 4 MiB, with both
aligned and unaligned buffers, to spot platforms where unaligned
accesses have a noticeable performance impact.

The copy length is hidden from the compiler, so the architecture's
out-of-line implementation is always measured rather than an inlined
constant-size copy.

Sample output on a RISC-V machine:
	# modprobe memcpy_kunit
	KTAP version 1
	1..1
	    KTAP version 1
	    # Subtest: memcpy
	    # module: memcpy_kunit
	    1..8
	    [...]
	    # memcpy_bench_test: memcpy: aligned copy of len 2: 3 MB/s
	    # memcpy_bench_test: memcpy: unaligned copy of len 2: 4 MB/s
	    # memcpy_bench_test: memcpy: aligned copy of len 64: 127 MB/s
	    # memcpy_bench_test: memcpy: unaligned copy of len 64: 117 MB/s
	    # memcpy_bench_test: memcpy: aligned copy of len 256: 508 MB/s
	    # memcpy_bench_test: memcpy: unaligned copy of len 256: 542 MB/s
	    # memcpy_bench_test: memcpy: aligned copy of len 4194304: 848 MB/s
	    # memcpy_bench_test: memcpy: unaligned copy of len 4194304: 699 MB/s
	    # memcpy_bench_test.speed: slow
	    ok 7 memcpy_bench_test
	    # memmove_bench_test: memmove: aligned move of len 64: 121 MB/s
	    # memmove_bench_test: memmove: unaligned move of len 64: 104 MB/s
	    # memmove_bench_test: memmove: aligned move of len 256: 445 MB/s
	    # memmove_bench_test: memmove: unaligned move of len 256: 410 MB/s
	    # memmove_bench_test: memmove: aligned move of len 4194304: 1419 MB/s
	    # memmove_bench_test: memmove: unaligned move of len 4194304: 1360 MB/s
	    # memmove_bench_test.speed: slow
	    ok 8 memmove_bench_test
	# memcpy: pass:8 fail:0 skip:0 total:8
	# Totals: pass:8 fail:0 skip:0 total:8
	ok 1 memcpy

Signed-off-by: Matteo Croce <technoboy85@gmail.com>
---
v6 -> v7:
- subject: "KUnit: memcpy: add benchmark" -> "kunit: memcpy: Add benchmark"
- memmove: make the aligned case actually aligned
- fix int overflow in throughput calculation, cap max size to 4 MiB
- hide copy length from the compiler (OPTIMIZER_HIDE_VAR)
- always register the bench cases, kunit_skip() when disabled
- Kconfig: move under MEMCPY_KUNIT_TEST, reword prompt and help
- update sample output

v6: https://lore.kernel.org/all/20260302023052.73943-1-teknoraver@meta.com/

 lib/Kconfig.debug        |  12 ++++
 lib/tests/memcpy_kunit.c | 120 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 132 insertions(+)

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 6b24a2c89b84..7884a9a0f78f 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3006,6 +3006,18 @@ config MEMCPY_KUNIT_TEST
 
 	  If unsure, say N.
 
+config MEMCPY_KUNIT_BENCHMARK
+	bool "Benchmark memcpy() and memmove() functions at runtime"
+	depends on MEMCPY_KUNIT_TEST
+	help
+	  Adds benchmark cases to the memcpy KUnit test suite, measuring
+	  the throughput of memcpy() and memmove() over several buffer
+	  sizes, with both aligned and unaligned buffers. This helps to
+	  spot architectures where unaligned accesses have a noticeable
+	  performance impact. Results are printed in the KUnit log.
+
+	  If unsure, say N.
+
 config MIN_HEAP_KUNIT_TEST
 	tristate "Min heap test" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/lib/tests/memcpy_kunit.c b/lib/tests/memcpy_kunit.c
index d36933554e46..7b7103a72ada 100644
--- a/lib/tests/memcpy_kunit.c
+++ b/lib/tests/memcpy_kunit.c
@@ -11,6 +11,7 @@
 #include <linux/mm.h>
 #include <linux/module.h>
 #include <linux/overflow.h>
+#include <linux/sizes.h>
 #include <linux/slab.h>
 #include <linux/types.h>
 #include <linux/vmalloc.h>
@@ -493,6 +494,123 @@ static void memmove_overlap_test(struct kunit *test)
 	}
 }
 
+#define COPIES_NUM	100
+#define BENCH_MAX_SIZE	min_t(size_t, SZ_4M, KMALLOC_MAX_SIZE)
+
+static void memcpy_bench_size_align(struct kunit *test, int size, bool unalign)
+{
+	u64 start, end, total_ns = 0;
+	char *dst, *src;
+	int ret = 0;
+
+	/* Prevent constant-size inlining: always call the arch memcpy() */
+	OPTIMIZER_HIDE_VAR(size);
+
+	dst = kzalloc(size, GFP_KERNEL);
+	if (!dst) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	src = kzalloc(size, GFP_KERNEL);
+	if (!src) {
+		ret = -ENOMEM;
+		goto out_free;
+	}
+
+	for (int i = 0; i < COPIES_NUM; i++) {
+		local_irq_disable();
+		start = ktime_get_ns();
+		memcpy(dst + unalign, src, size - unalign);
+		end = ktime_get_ns();
+		local_irq_enable();
+		total_ns += end - start;
+	}
+
+	/* Avoid division by zero */
+	if (!total_ns)
+		total_ns = 1;
+
+	kunit_info(test, "memcpy: %saligned copy of len %d: %llu MB/s",
+		   unalign ? "un" : "", size,
+		   div64_u64((u64)COPIES_NUM * size * NSEC_PER_SEC,
+			     total_ns * 1024 * 1024));
+
+	kfree(src);
+
+out_free:
+	kfree(dst);
+
+out:
+	KUNIT_ASSERT_EQ(test, ret, 0);
+}
+
+static void memcpy_bench_size(struct kunit *test, int size)
+{
+	memcpy_bench_size_align(test, size, false);
+	memcpy_bench_size_align(test, size, true);
+}
+
+static void memcpy_bench_test(struct kunit *test)
+{
+	if (!IS_ENABLED(CONFIG_MEMCPY_KUNIT_BENCHMARK))
+		kunit_skip(test, "not enabled");
+
+	memcpy_bench_size(test, 2);
+	memcpy_bench_size(test, 64);
+	memcpy_bench_size(test, 256);
+	memcpy_bench_size(test, BENCH_MAX_SIZE);
+}
+
+static void memmove_bench_size_align(struct kunit *test, int size, bool unalign)
+{
+	u64 start, end, total_ns = 0;
+	char *buf;
+	/* Keep the destination word aligned in the aligned case */
+	const int shift = ALIGN(size / 10, sizeof(long));
+
+	/* Prevent constant-size inlining: always call the arch memmove() */
+	OPTIMIZER_HIDE_VAR(size);
+
+	buf = kzalloc(size, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, buf);
+
+	for (int i = 0; i < COPIES_NUM; i++) {
+		local_irq_disable();
+		start = ktime_get_ns();
+		memmove(buf + shift + unalign, buf, size - shift - unalign);
+		end = ktime_get_ns();
+		local_irq_enable();
+		total_ns += end - start;
+	}
+
+	if (!total_ns)
+		total_ns = 1;
+
+	kunit_info(test, "memmove: %saligned move of len %d: %llu MB/s",
+		   unalign ? "un" : "", size,
+		   div64_u64((u64)COPIES_NUM * (size - shift) * NSEC_PER_SEC,
+			     total_ns * 1024 * 1024));
+
+	kfree(buf);
+}
+
+static void memmove_bench_size(struct kunit *test, int size)
+{
+	memmove_bench_size_align(test, size, false);
+	memmove_bench_size_align(test, size, true);
+}
+
+static void memmove_bench_test(struct kunit *test)
+{
+	if (!IS_ENABLED(CONFIG_MEMCPY_KUNIT_BENCHMARK))
+		kunit_skip(test, "not enabled");
+
+	memmove_bench_size(test, 64);
+	memmove_bench_size(test, 256);
+	memmove_bench_size(test, BENCH_MAX_SIZE);
+}
+
 static struct kunit_case memcpy_test_cases[] = {
 	KUNIT_CASE(memset_test),
 	KUNIT_CASE(memcpy_test),
@@ -500,6 +618,8 @@ static struct kunit_case memcpy_test_cases[] = {
 	KUNIT_CASE_SLOW(memmove_test),
 	KUNIT_CASE_SLOW(memmove_large_test),
 	KUNIT_CASE_SLOW(memmove_overlap_test),
+	KUNIT_CASE_SLOW(memcpy_bench_test),
+	KUNIT_CASE_SLOW(memmove_bench_test),
 	{}
 };
 
-- 
2.56.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-11-10  2:21 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10  2:16 [PATCH v7] kunit: memcpy: Add benchmark Matteo Croce
2026-11-10  2:20 ` Matteo Croce

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®