All of lore.kernel.org
 help / color / mirror / Atom feed
* [patch 0/3] lib: add synthetic MM benchmarks
@ 2026-07-31  4:26 David Rientjes
  2026-07-31  4:26 ` [patch 1/3] lib: test_vmstat: add synthetic benchmark for vm stats David Rientjes
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: David Rientjes @ 2026-07-31  4:26 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Christoph Lameter
  Cc: Vlastimil Babka, Mathieu Desnoyers, linux-mm, linux-kernel

A blast to the past :)

We've been carrying these synthetic benchmarks in our kernel tree since 
2009 because they've been helpful to identify regressions in hot paths, as 
well as quantifying any improvements that have been made for new changes.  
Hopefully they can be useful to others as well.

The synthetic benchmarks are run by loading the module at runtime.  The 
modprobe will fail intentionally so that the module gets insta-unloaded.  
The test results are emitted to the kernel log.

Proposed with permission from Christoph.
---
 lib/Kconfig.debug    |  30 ++++
 lib/Makefile         |   3 +
 lib/test_pagealloc.c | 337 ++++++++++++++++++++++++++++++++++++++
 lib/test_slab.c      | 375 +++++++++++++++++++++++++++++++++++++++++++
 lib/test_vmstat.c    |  95 +++++++++++
 5 files changed, 840 insertions(+)
 create mode 100644 lib/test_pagealloc.c
 create mode 100644 lib/test_slab.c
 create mode 100644 lib/test_vmstat.c

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

* [patch 1/3] lib: test_vmstat: add synthetic benchmark for vm stats
  2026-07-31  4:26 [patch 0/3] lib: add synthetic MM benchmarks David Rientjes
@ 2026-07-31  4:26 ` David Rientjes
  2026-08-01 13:20   ` Usama Arif
  2026-07-31  4:26 ` [patch 2/3] lib: test_slab: add synthetic benchmark for slab David Rientjes
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 6+ messages in thread
From: David Rientjes @ 2026-07-31  4:26 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Christoph Lameter
  Cc: Vlastimil Babka, Mathieu Desnoyers, linux-mm, linux-kernel

From: Christoph Lameter <cl@gentwo.org>

Add a synthetic benchmark that can be used to measure performance of VM
statistics.  This is used to analyze any improvements or regressions in
functions that are frequently used in hot code paths.

The test is run by inserting the module: modprobe test_vmstat
The module insertion will always fail so that it is automatically
unloaded; the results are in the kernel log.

Signed-off-by: Christoph Lameter <cl@gentwo.org>
Signed-off-by: David Rientjes <rientjes@google.com>
---
 lib/Kconfig.debug | 10 +++++
 lib/Makefile      |  1 +
 lib/test_vmstat.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 106 insertions(+)
 create mode 100644 lib/test_vmstat.c

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 1244dcac2294..6d743c8e27fa 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3427,6 +3427,16 @@ config TEST_KEXEC_HANDOVER
 
 	  If unsure, say N.
 
+config TEST_VMSTAT
+	tristate "VM Statistics Benchmark"
+	default n
+	help
+	  A synthetic benchmark measuring the performance of VM statistics,
+	  useful to analyze any improvements or regressions in functions that
+	  are frequently used in hot code paths.
+
+	  If unsure, say N.
+
 config RATELIMIT_KUNIT_TEST
 	tristate "KUnit Test for correctness and stress of ratelimit" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/lib/Makefile b/lib/Makefile
index 7f75cc6edf94..5f3ebabd0224 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -105,6 +105,7 @@ obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
 obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
 obj-$(CONFIG_TEST_OBJPOOL) += test_objpool.o
 obj-$(CONFIG_TEST_KEXEC_HANDOVER) += test_kho.o
+obj-$(CONFIG_TEST_VMSTAT) += test_vmstat.o
 
 obj-$(CONFIG_TEST_FPU) += test_fpu.o
 test_fpu-y := test_fpu_glue.o test_fpu_impl.o
diff --git a/lib/test_vmstat.c b/lib/test_vmstat.c
new file mode 100644
index 000000000000..f63f439ab9d2
--- /dev/null
+++ b/lib/test_vmstat.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Test module for in-kernel synthetic vm statistics performance.
+ *
+ * execute
+ *
+ *	modprobe test_vmstat
+ *
+ * to run this test
+ *
+ * (C) 2009 Linux Foundation, Christoph Lameter <cl@gentwo.org>
+ */
+
+#include <linux/jiffies.h>
+#include <linux/compiler.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <asm/timex.h>
+
+#define TEST_COUNT 10000
+
+static int vmstat_test_init(void)
+{
+	unsigned int i;
+	cycles_t time1, time2, time;
+	int rem;
+	struct page *page = alloc_page(GFP_KERNEL);
+
+	pr_alert("VMstat testing\n");
+	pr_alert("=====================\n");
+	pr_alert("1. inc_zone_page_state() then dec_zone_page_state()\n");
+	time1 = get_cycles();
+	for (i = 0; i < TEST_COUNT; i++)
+		inc_zone_page_state(page, NR_FREE_CMA_PAGES);
+
+	time2 = get_cycles();
+	time = time2 - time1;
+
+	pr_alert("%i times inc_zone_page_state() ", i);
+	time = div_u64_rem(time, TEST_COUNT, &rem);
+	pr_cont("-> %llu cycles ", (unsigned long long) time);
+
+	time1 = get_cycles();
+	for (i = 0; i < TEST_COUNT; i++)
+		__dec_zone_page_state(page, NR_FREE_CMA_PAGES);
+
+	time2 = get_cycles();
+	time = time2 - time1;
+
+	pr_cont("__dec_z_p_s() ");
+	time = div_u64_rem(time, TEST_COUNT, &rem);
+	pr_cont("-> %llu cycles\n",  (unsigned long long) time);
+
+	pr_alert("2. inc_zone_page_state()/dec_zone_page_state()\n");
+	time1 = get_cycles();
+	for (i = 0; i < TEST_COUNT; i++) {
+		inc_zone_page_state(page, NR_FREE_CMA_PAGES);
+		dec_zone_page_state(page, NR_FREE_CMA_PAGES);
+	}
+
+	time2 = get_cycles();
+	time = time2 - time1;
+
+	pr_alert("%i times inc/dec ", i);
+	time = div_u64_rem(time, TEST_COUNT, &rem);
+	pr_cont("-> %llu cycles\n",  (unsigned long long) time);
+
+	pr_alert("3. count_vm_event()\n");
+	time1 = get_cycles();
+	for (i = 0; i < TEST_COUNT; i++)
+		count_vm_event(SLABS_SCANNED);
+
+	time2 = get_cycles();
+	time = time2 - time1;
+
+	count_vm_events(SLABS_SCANNED, -TEST_COUNT);
+	pr_alert("%i count_vm_events ", i);
+	time = div_u64_rem(time, TEST_COUNT, &rem);
+	pr_cont("-> %llu cycles\n",  (unsigned long long) time);
+	__free_page(page);
+	return -EAGAIN; /* Fail will directly unload the module */
+}
+
+static void vmstat_test_exit(void)
+{
+	pr_alert("test exit\n");
+}
+
+module_init(vmstat_test_init)
+module_exit(vmstat_test_exit)
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Christoph Lameter");
+MODULE_DESCRIPTION("VM statistics test");


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

* [patch 2/3] lib: test_slab: add synthetic benchmark for slab
  2026-07-31  4:26 [patch 0/3] lib: add synthetic MM benchmarks David Rientjes
  2026-07-31  4:26 ` [patch 1/3] lib: test_vmstat: add synthetic benchmark for vm stats David Rientjes
@ 2026-07-31  4:26 ` David Rientjes
  2026-07-31  4:27 ` [patch 3/3] lib: test_pagealloc: add synthetic benchmark for page allocator David Rientjes
  2026-07-31  9:00 ` [patch 0/3] lib: add synthetic MM benchmarks David Hildenbrand (Arm)
  3 siblings, 0 replies; 6+ messages in thread
From: David Rientjes @ 2026-07-31  4:26 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Christoph Lameter
  Cc: Vlastimil Babka, Mathieu Desnoyers, linux-mm, linux-kernel

From: Christoph Lameter <cl@gentwo.org>

Add a synthetic benchmark that can be used to measure performance of the
slab allocator.  This is used to analyze any improvements or regressions
in slab allocation or freeing functions.

The test is run by inserting the module: modprobe test_slab
The module insertion will always fail so that it is automatically
unloaded; the results are in the kernel log.

Signed-off-by: Christoph Lameter <cl@gentwo.org>
Signed-off-by: David Rientjes <rientjes@google.com>
---
 lib/Kconfig.debug |  10 ++
 lib/Makefile      |   1 +
 lib/test_slab.c   | 375 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 386 insertions(+)
 create mode 100644 lib/test_slab.c

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 6d743c8e27fa..a7e738e6afa8 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3437,6 +3437,16 @@ config TEST_VMSTAT
 
 	  If unsure, say N.
 
+config TEST_SLAB
+	tristate "Slab Allocator Benchmark"
+	default n
+	help
+	  A synthetic benchmark that measures slab allocator performance,
+	  specifically focusing on single thread testing by repeatedly
+	  allocating then freeing memory and concurrent or remote allocations.
+
+	  If unsure, say N.
+
 config RATELIMIT_KUNIT_TEST
 	tristate "KUnit Test for correctness and stress of ratelimit" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/lib/Makefile b/lib/Makefile
index 5f3ebabd0224..fe54690d130c 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -106,6 +106,7 @@ obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
 obj-$(CONFIG_TEST_OBJPOOL) += test_objpool.o
 obj-$(CONFIG_TEST_KEXEC_HANDOVER) += test_kho.o
 obj-$(CONFIG_TEST_VMSTAT) += test_vmstat.o
+obj-$(CONFIG_TEST_SLAB) += test_slab.o
 
 obj-$(CONFIG_TEST_FPU) += test_fpu.o
 test_fpu-y := test_fpu_glue.o test_fpu_impl.o
diff --git a/lib/test_slab.c b/lib/test_slab.c
new file mode 100644
index 000000000000..a43b0d73daab
--- /dev/null
+++ b/lib/test_slab.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Test module for synthetic in kernel slab allocator testing.
+ *
+ * The test is triggered by loading the module (which will fail).
+ *
+ * (C) 2009 Linux Foundation <cl@gentwo.org>
+ */
+
+#include <linux/jiffies.h>
+#include <linux/compiler.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <asm/timex.h>
+
+#define TEST_COUNT 10000
+
+#ifdef CONFIG_SMP
+#include <linux/completion.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+#include <linux/kthread.h>
+
+static struct test_struct {
+	struct task_struct *task;
+	int cpu;
+	int size;
+	int count;
+	void **v;
+	void (*test_p1)(struct test_struct *t);
+	void (*test_p2)(struct test_struct *t);
+	unsigned long start1;
+	unsigned long stop1;
+	unsigned long start2;
+	unsigned long stop2;
+} test[NR_CPUS];
+
+/*
+ * Allocate TEST_COUNT objects on cpus > 0 and then all the
+ * objects later on cpu 0
+ */
+static void remote_free_test_p1(struct test_struct *t)
+{
+	int i;
+
+	/* Perform no allocations on cpu 0 */
+	for (i = 0; i < t->count; i++) {
+		u8 *p;
+
+		if (smp_processor_id()) {
+			p = kmalloc(t->size, GFP_KERNEL);
+			/* Use object */
+			*p = 17;
+		} else
+			p = NULL;
+		t->v[i] = p;
+	}
+}
+
+static void remote_free_test_p2(struct test_struct *t)
+{
+	int i;
+	int cpu;
+
+	/* All frees are completed on cpu zero */
+	if (smp_processor_id())
+		return;
+
+	for_each_online_cpu(cpu)
+		for (i = 0; i < t->count; i++) {
+			u8 *p = test[cpu].v[i];
+
+			if (!p)
+				continue;
+
+			*p = 16;
+			kfree(p);
+		}
+}
+
+/*
+ * Allocate TEST_COUNT objects on cpu 0 and free them immediately on the
+ * other processors.
+ */
+static void alloc_n_free_test_p1(struct test_struct *t)
+{
+	int i;
+	int cpu;
+	char *p;
+
+	if (smp_processor_id()) {
+		/* Consumer */
+		for (i = 0; i < t->count / num_online_cpus(); i++) {
+			do {
+				p = t->v[i];
+				if (!p)
+					cpu_relax();
+				else
+					*p = 17;
+			} while (!p);
+			kfree(p);
+			t->v[i] = NULL;
+		}
+		return;
+	}
+	/* Producer */
+	for (i = 0; i < t->count; i++) {
+		for_each_online_cpu(cpu) {
+			if (cpu) {
+				p = kmalloc(t->size, GFP_KERNEL);
+				/* Use object */
+				*p = 17;
+				test[cpu].v[i] = p;
+			}
+		}
+	}
+}
+
+/*
+ * Allocate TEST_COUNT objects and later free them all again
+ */
+static void kmalloc_alloc_then_free_test_p1(struct test_struct *t)
+{
+	int i;
+
+	for (i = 0; i < t->count; i++) {
+		u8 *p = kmalloc(t->size, GFP_KERNEL);
+
+		*p = 14;
+		t->v[i] = p;
+	}
+}
+
+static void kmalloc_alloc_then_free_test_p2(struct test_struct *t)
+{
+	int i;
+
+	for (i = 0; i < t->count; i++) {
+		u8 *p = t->v[i];
+
+		*p = 13;
+		kfree(p);
+	}
+}
+
+/*
+ * Allocate TEST_COUNT objects. Free them immediately.
+ */
+static void kmalloc_alloc_free_test_p1(struct test_struct *t)
+{
+	int i;
+
+	for (i = 0; i < TEST_COUNT; i++) {
+		u8 *p = kmalloc(t->size, GFP_KERNEL);
+
+		*p = 12;
+		kfree(p);
+	}
+}
+
+static atomic_t tests_running;
+static atomic_t phase1_complete;
+static DECLARE_COMPLETION(completion1);
+static DECLARE_COMPLETION(completion2);
+static DECLARE_COMPLETION(completion3);
+
+static int test_func(void *private)
+{
+	struct test_struct *t = private;
+	cpumask_t newmask = CPU_MASK_NONE;
+
+	cpumask_set_cpu(t->cpu, &newmask);
+	set_cpus_allowed_ptr(current, &newmask);
+	t->v = kcalloc(t->count, sizeof(void *), GFP_KERNEL);
+
+	atomic_inc(&tests_running);
+	wait_for_completion(&completion1);
+	t->start1 = get_cycles();
+	t->test_p1(t);
+	t->stop1 = get_cycles();
+	atomic_inc(&phase1_complete);
+	wait_for_completion(&completion2);
+	t->start2 = get_cycles();
+	if (t->test_p2)
+		t->test_p2(t);
+	t->stop2 = get_cycles();
+	atomic_dec(&tests_running);
+	wait_for_completion(&completion3);
+	kfree(t->v);
+	while (!kthread_should_stop()) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+	}
+	return 0;
+}
+
+static void do_concurrent_test(void (*p1)(struct test_struct *),
+		void (*p2)(struct test_struct *),
+		int size, const char *name)
+{
+	int cpu;
+	unsigned long time1 = 0;
+	unsigned long time2 = 0;
+	unsigned long sum1 = 0;
+	unsigned long sum2 = 0;
+
+	atomic_set(&tests_running, 0);
+	atomic_set(&phase1_complete, 0);
+	init_completion(&completion1);
+	init_completion(&completion2);
+	init_completion(&completion3);
+
+	for_each_online_cpu(cpu) {
+		struct test_struct *t = &test[cpu];
+
+		t->cpu = cpu;
+		t->count = TEST_COUNT;
+		t->test_p1 = p1;
+		t->test_p2 = p2;
+		t->size = size;
+		t->task = kthread_run(test_func, t, "test%d", cpu);
+		if (IS_ERR(t->task)) {
+			pr_err("Failed to start test func\n");
+			return;
+		}
+	}
+
+	/* Wait till all processes are running */
+	while (atomic_read(&tests_running) < num_online_cpus()) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+	}
+	complete_all(&completion1);
+
+	/* Wait till all processes have completed phase 1 */
+	while (atomic_read(&phase1_complete) < num_online_cpus()) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+	}
+	complete_all(&completion2);
+
+	/* Wait till all processes have completed phase 2 */
+	while (atomic_read(&tests_running)) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+	}
+	complete_all(&completion3);
+
+	for_each_online_cpu(cpu)
+		kthread_stop(test[cpu].task);
+
+	pr_alert("%s(%d):", name, size);
+	for_each_online_cpu(cpu) {
+		struct test_struct *t = &test[cpu];
+
+		time1 = t->stop1 - t->start1;
+		time2 = t->stop2 - t->start2;
+		sum1 += time1;
+		sum2 += time2;
+		pr_cont(" %d=%lu", cpu, time1 / TEST_COUNT);
+		if (p2)
+			pr_cont("/%lu", time2 / TEST_COUNT);
+	}
+	pr_cont(" Average=%lu", sum1 / num_online_cpus() / TEST_COUNT);
+	if (p2)
+		pr_cont("/%lu", sum2 / num_online_cpus() / TEST_COUNT);
+	pr_cont("\n");
+	schedule_timeout(200);
+}
+#endif
+
+static int slab_test_init(void)
+{
+	void **v = kmalloc_array(TEST_COUNT, sizeof(void *), GFP_KERNEL);
+	unsigned int i;
+	cycles_t time1, time2, time;
+	int rem;
+	int size;
+
+	pr_alert("test init\n");
+
+	pr_alert("Single thread testing\n");
+	pr_alert("=====================\n");
+	pr_alert("1. Kmalloc: Repeatedly allocate then free test\n");
+	for (size = 8; size <= PAGE_SIZE << 2; size <<= 1) {
+		time1 = get_cycles();
+		for (i = 0; i < TEST_COUNT; i++) {
+			u8 *p = kmalloc(size, GFP_KERNEL);
+
+			*p = 22;
+			v[i] = p;
+		}
+		time2 = get_cycles();
+		time = time2 - time1;
+
+		pr_alert("%i times kmalloc(%d) ", i, size);
+		time = div_u64_rem(time, TEST_COUNT, &rem);
+		pr_cont("-> %llu cycles ", (unsigned long long) time);
+
+		time1 = get_cycles();
+		for (i = 0; i < TEST_COUNT; i++) {
+			u8 *p = v[i];
+
+			*p = 23;
+			kfree(p);
+		}
+		time2 = get_cycles();
+		time = time2 - time1;
+
+		pr_cont("kfree ");
+		time = div_u64_rem(time, TEST_COUNT, &rem);
+		pr_cont("-> %llu cycles\n", (unsigned long long) time);
+	}
+
+	pr_alert("2. Kmalloc: alloc/free test\n");
+	for (size = 8; size <= PAGE_SIZE << 2; size <<= 1) {
+		time1 = get_cycles();
+		for (i = 0; i < TEST_COUNT; i++) {
+			u8 *p = kmalloc(size, GFP_KERNEL);
+
+			kfree(p);
+		}
+		time2 = get_cycles();
+		time = time2 - time1;
+
+		pr_alert("%i times kmalloc(%d)/kfree ", i, size);
+		time = div_u64_rem(time, TEST_COUNT, &rem);
+		pr_cont("-> %llu cycles\n", (unsigned long long) time);
+	}
+	kfree(v);
+#ifdef CONFIG_SMP
+	pr_info("Concurrent allocs\n");
+	pr_info("=================\n");
+	for (i = 3; i <= PAGE_SHIFT; i++) {
+		do_concurrent_test(kmalloc_alloc_then_free_test_p1,
+			kmalloc_alloc_then_free_test_p2,
+			1 << i, "Kmalloc N*alloc N*free");
+	}
+	for (i = 3; i <= PAGE_SHIFT; i++) {
+		do_concurrent_test(kmalloc_alloc_free_test_p1, NULL,
+			1 << i, "Kmalloc N*(alloc free)");
+	}
+
+	pr_info("Remote free test\n");
+	pr_info("================\n");
+	for (i = 3; i <= PAGE_SHIFT; i++) {
+		do_concurrent_test(remote_free_test_p1,
+				remote_free_test_p2,
+			1 << i, "N*remote free");
+	}
+
+	pr_info("1 alloc N free test\n");
+	pr_info("===================\n");
+	for (i = 3; i <= PAGE_SHIFT; i++) {
+		do_concurrent_test(alloc_n_free_test_p1,
+				NULL,
+			1 << i, "1 alloc N free");
+	}
+
+#endif
+	return -EAGAIN; /* Fail will directly unload the module */
+}
+
+static void slab_test_exit(void)
+{
+	pr_alert("test exit\n");
+}
+
+module_init(slab_test_init)
+module_exit(slab_test_exit)
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Christoph Lameter and Mathieu Desnoyers");
+MODULE_DESCRIPTION("SLAB test");


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

* [patch 3/3] lib: test_pagealloc: add synthetic benchmark for page allocator
  2026-07-31  4:26 [patch 0/3] lib: add synthetic MM benchmarks David Rientjes
  2026-07-31  4:26 ` [patch 1/3] lib: test_vmstat: add synthetic benchmark for vm stats David Rientjes
  2026-07-31  4:26 ` [patch 2/3] lib: test_slab: add synthetic benchmark for slab David Rientjes
@ 2026-07-31  4:27 ` David Rientjes
  2026-07-31  9:00 ` [patch 0/3] lib: add synthetic MM benchmarks David Hildenbrand (Arm)
  3 siblings, 0 replies; 6+ messages in thread
From: David Rientjes @ 2026-07-31  4:27 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Christoph Lameter
  Cc: Vlastimil Babka, Mathieu Desnoyers, linux-mm, linux-kernel

From: Christoph Lameter <cl@gentwo.org>

Add a synthetic benchmark that can be used to measure performance of the
buddy allocator, including repeatedly allocating and freeing memory as
well as concurrent and remote allocations.

The test is run by inserting the module: modprobe test_pagealloc
The module insertion will always fail so that it is automatically
unloaded; the results are in the kernel log.

Signed-off-by: Christoph Lameter <cl@gentwo.org>
Signed-off-by: David Rientjes <rientjes@google.com>
---
 lib/Kconfig.debug    |  10 ++
 lib/Makefile         |   1 +
 lib/test_pagealloc.c | 337 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 348 insertions(+)
 create mode 100644 lib/test_pagealloc.c

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index a7e738e6afa8..70873aac675f 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3447,6 +3447,16 @@ config TEST_SLAB
 
 	  If unsure, say N.
 
+config TEST_PAGEALLOC
+	tristate "Page Allocator Benchmark"
+	default n
+	help
+	  A benchmark measuring the performance of the page allocator, useful
+	  to analyze any improvements or regressions when repeatedly allocating
+	  and freeing memory as well as concurrent and remote allocations.
+
+	  If unsure, say N.
+
 config RATELIMIT_KUNIT_TEST
 	tristate "KUnit Test for correctness and stress of ratelimit" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/lib/Makefile b/lib/Makefile
index fe54690d130c..17c164e145ad 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -107,6 +107,7 @@ obj-$(CONFIG_TEST_OBJPOOL) += test_objpool.o
 obj-$(CONFIG_TEST_KEXEC_HANDOVER) += test_kho.o
 obj-$(CONFIG_TEST_VMSTAT) += test_vmstat.o
 obj-$(CONFIG_TEST_SLAB) += test_slab.o
+obj-$(CONFIG_TEST_PAGEALLOC) += test_pagealloc.o
 
 obj-$(CONFIG_TEST_FPU) += test_fpu.o
 test_fpu-y := test_fpu_glue.o test_fpu_impl.o
diff --git a/lib/test_pagealloc.c b/lib/test_pagealloc.c
new file mode 100644
index 000000000000..30514df52c5b
--- /dev/null
+++ b/lib/test_pagealloc.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Test module for in kernel synthetic page allocator testing.
+ *
+ * Compiled as a module. The module needs to be loaded to run.
+ *
+ * (C) 2009 Linux Foundation, Christoph Lameter <cl@gentwo.org>
+ */
+
+#include <linux/jiffies.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <asm/timex.h>
+
+#define TEST_COUNT 1000
+
+#define CONCURRENT_MAX_ORDER 6
+
+#ifdef CONFIG_SMP
+#include <linux/completion.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+#include <linux/kthread.h>
+
+static struct test_struct {
+	struct task_struct *task;
+	int cpu;
+	int order;
+	int count;
+	struct page **v;
+	void (*test_p1)(struct test_struct *t);
+	void (*test_p2)(struct test_struct *t);
+	unsigned long start1;
+	unsigned long stop1;
+	unsigned long start2;
+	unsigned long stop2;
+} test[NR_CPUS];
+
+/*
+ * Allocate TEST_COUNT objects on cpus > 0 and then all the
+ * objects later on cpu 0
+ */
+static void remote_free_test_p1(struct test_struct *t)
+{
+	int i;
+
+	/* Perform no allocations on cpu 0 */
+	for (i = 0; i < t->count; i++) {
+		struct page *p;
+
+		if (smp_processor_id()) {
+			p = alloc_pages(GFP_KERNEL | __GFP_COMP, t->order);
+			/* Use object */
+			memset(page_address(p), 17, 4);
+		} else
+			p = NULL;
+		t->v[i] = p;
+	}
+}
+
+static void remote_free_test_p2(struct test_struct *t)
+{
+	int i;
+	int cpu;
+
+	/* All frees are completed on cpu zero */
+	if (smp_processor_id())
+		return;
+
+	for_each_online_cpu(cpu)
+		for (i = 0; i < t->count; i++) {
+			struct page *p = test[cpu].v[i];
+
+			if (!p)
+				continue;
+
+			__free_pages(p, t->order);
+		}
+}
+
+/*
+ * Allocate TEST_COUNT objects and later free them all again
+ */
+static void alloc_then_free_test_p1(struct test_struct *t)
+{
+	int i;
+
+	for (i = 0; i < t->count; i++) {
+		struct page *p = alloc_pages(GFP_KERNEL | __GFP_COMP, t->order);
+
+		memset(page_address(p), 14, 4);
+		t->v[i] = p;
+	}
+}
+
+static void alloc_then_free_test_p2(struct test_struct *t)
+{
+	int i;
+
+	for (i = 0; i < t->count; i++) {
+		struct page *p = t->v[i];
+
+		__free_pages(p, t->order);
+	}
+}
+
+/*
+ * Allocate TEST_COUNT objects. Free them immediately.
+ */
+static void alloc_free_test_p1(struct test_struct *t)
+{
+	int i;
+
+	for (i = 0; i < TEST_COUNT; i++) {
+		struct page *p = alloc_pages(GFP_KERNEL | __GFP_COMP, t->order);
+
+		memset(page_address(p), 12, 4);
+		__free_pages(p, t->order);
+	}
+}
+
+static atomic_t tests_running;
+static atomic_t phase1_complete;
+static DECLARE_COMPLETION(completion1);
+static DECLARE_COMPLETION(completion2);
+static DECLARE_COMPLETION(completion3);
+
+static int test_func(void *private)
+{
+	struct test_struct *t = private;
+	cpumask_t newmask = CPU_MASK_NONE;
+
+	cpumask_set_cpu(t->cpu, &newmask);
+	set_cpus_allowed_ptr(current, &newmask);
+	t->v = kmalloc_array(t->count, sizeof(struct page *), GFP_KERNEL);
+
+	atomic_inc(&tests_running);
+	wait_for_completion(&completion1);
+	t->start1 = get_cycles();
+	t->test_p1(t);
+	t->stop1 = get_cycles();
+	atomic_inc(&phase1_complete);
+	wait_for_completion(&completion2);
+	t->start2 = get_cycles();
+	if (t->test_p2)
+		t->test_p2(t);
+	t->stop2 = get_cycles();
+	atomic_dec(&tests_running);
+	wait_for_completion(&completion3);
+	kfree(t->v);
+	while (!kthread_should_stop()) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+	}
+	return 0;
+}
+
+static void do_concurrent_test(void (*p1)(struct test_struct *),
+		void (*p2)(struct test_struct *),
+		int order, const char *name)
+{
+	int cpu;
+	unsigned long time1 = 0;
+	unsigned long time2 = 0;
+	unsigned long sum1 = 0;
+	unsigned long sum2 = 0;
+
+	atomic_set(&tests_running, 0);
+	atomic_set(&phase1_complete, 0);
+	init_completion(&completion1);
+	init_completion(&completion2);
+	init_completion(&completion3);
+
+	for_each_online_cpu(cpu) {
+		struct test_struct *t = &test[cpu];
+
+		t->cpu = cpu;
+		t->count = TEST_COUNT;
+		t->test_p1 = p1;
+		t->test_p2 = p2;
+		t->order = order;
+		t->task = kthread_run(test_func, t, "test%d", cpu);
+		if (IS_ERR(t->task)) {
+			pr_err("Failed to start test func\n");
+			return;
+		}
+	}
+
+	/* Wait till all processes are running */
+	while (atomic_read(&tests_running) < num_online_cpus()) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+	}
+	complete_all(&completion1);
+
+	/* Wait till all processes have completed phase 1 */
+	while (atomic_read(&phase1_complete) < num_online_cpus()) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+	}
+	complete_all(&completion2);
+
+	/* Wait till all processes have completed phase 2 */
+	while (atomic_read(&tests_running)) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(10);
+	}
+	complete_all(&completion3);
+
+	for_each_online_cpu(cpu)
+		kthread_stop(test[cpu].task);
+
+	pr_alert("%s(%d):", name, order);
+	for_each_online_cpu(cpu) {
+		struct test_struct *t = &test[cpu];
+
+		time1 = t->stop1 - t->start1;
+		time2 = t->stop2 - t->start2;
+		sum1 += time1;
+		sum2 += time2;
+		pr_cont(" %d=%lu", cpu, time1 / TEST_COUNT);
+		if (p2)
+			pr_cont("/%lu", time2 / TEST_COUNT);
+	}
+	pr_cont(" Average=%lu", sum1 / num_online_cpus() / TEST_COUNT);
+	if (p2)
+		pr_cont("/%lu", sum2 / num_online_cpus() / TEST_COUNT);
+	pr_cont("\n");
+	schedule_timeout(200);
+}
+#endif
+
+static int pagealloc_test_init(void)
+{
+	void **v = kmalloc_array(TEST_COUNT, sizeof(void *), GFP_KERNEL);
+	unsigned int i;
+	cycles_t time1, time2, time;
+	int rem;
+	int order;
+
+	pr_alert("test init\n");
+
+	pr_alert("Single thread testing\n");
+	pr_alert("=====================\n");
+	pr_alert("1. Repeatedly allocate then free test\n");
+	for (order = 0; order <= MAX_PAGE_ORDER; order++) {
+		time1 = get_cycles();
+		for (i = 0; i < TEST_COUNT; i++) {
+			struct page *p = alloc_pages(GFP_KERNEL | __GFP_COMP,
+						order);
+
+			if (!p) {
+				pr_err("Cannot allocate order=%d\n", order);
+				break;
+			}
+
+			/* Touch page */
+			memset(page_address(p), 22, 4);
+			v[i] = p;
+		}
+		time2 = get_cycles();
+		time = time2 - time1;
+
+		pr_alert("%i times alloc_page(,%d) ", i, order);
+		time = div_u64_rem(time, TEST_COUNT, &rem);
+		pr_cont("-> %llu cycles ", (unsigned long long) time);
+
+		time1 = get_cycles();
+		for (i = 0; i < TEST_COUNT; i++) {
+			struct page *p = v[i];
+
+			__free_pages(p, order);
+		}
+		time2 = get_cycles();
+		time = time2 - time1;
+
+		pr_cont("__free_pages(,%d)", order);
+		time = div_u64_rem(time, TEST_COUNT, &rem);
+		pr_cont("-> %llu cycles\n", (unsigned long long) time);
+	}
+
+	pr_alert("2. alloc/free test\n");
+	for (order = 0; order <= MAX_PAGE_ORDER; order++) {
+		time1 = get_cycles();
+		for (i = 0; i < TEST_COUNT; i++) {
+			struct page *p = alloc_pages(GFP_KERNEL | __GFP_COMP,
+						     order);
+			__free_pages(p, order);
+		}
+		time2 = get_cycles();
+		time = time2 - time1;
+
+		pr_alert("%i times alloc( ,%d)/free ", i, order);
+		time = div_u64_rem(time, TEST_COUNT, &rem);
+		pr_cont("-> %llu cycles\n", (unsigned long long) time);
+	}
+	kfree(v);
+#ifdef CONFIG_SMP
+	pr_info("Concurrent allocs\n");
+	pr_info("=================\n");
+	for (order = 0; order < CONCURRENT_MAX_ORDER; order++) {
+		do_concurrent_test(alloc_then_free_test_p1,
+			alloc_then_free_test_p2,
+			order, "Page alloc N*alloc N*free");
+	}
+	pr_info("----Fastpath---\n");
+	for (order = 0; order < CONCURRENT_MAX_ORDER; order++) {
+		do_concurrent_test(alloc_free_test_p1, NULL,
+			order, "Page N*(alloc free)");
+	}
+
+	pr_info("Remote free test\n");
+	pr_info("================\n");
+	for (order = 0; order < CONCURRENT_MAX_ORDER; order++) {
+		do_concurrent_test(remote_free_test_p1,
+				remote_free_test_p2,
+			order, "N*remote free");
+	}
+
+#endif
+
+	return -EAGAIN; /* Fail will directly unload the module */
+}
+
+static void pagealloc_test_exit(void)
+{
+	pr_alert("test exit\n");
+}
+
+module_init(pagealloc_test_init)
+module_exit(pagealloc_test_exit)
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Christoph Lameter");
+MODULE_DESCRIPTION("page allocator performance test");


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

* Re: [patch 0/3] lib: add synthetic MM benchmarks
  2026-07-31  4:26 [patch 0/3] lib: add synthetic MM benchmarks David Rientjes
                   ` (2 preceding siblings ...)
  2026-07-31  4:27 ` [patch 3/3] lib: test_pagealloc: add synthetic benchmark for page allocator David Rientjes
@ 2026-07-31  9:00 ` David Hildenbrand (Arm)
  3 siblings, 0 replies; 6+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-31  9:00 UTC (permalink / raw)
  To: David Rientjes, Andrew Morton, Christoph Lameter
  Cc: Vlastimil Babka, Mathieu Desnoyers, linux-mm, linux-kernel

On 7/31/26 06:26, David Rientjes wrote:
> A blast to the past :)
> 

:)

> We've been carrying these synthetic benchmarks in our kernel tree since 
> 2009 because they've been helpful to identify regressions in hot paths, as 
> well as quantifying any improvements that have been made for new changes.  
> Hopefully they can be useful to others as well.
> 
> The synthetic benchmarks are run by loading the module at runtime.  The 
> modprobe will fail intentionally so that the module gets insta-unloaded.  
> The test results are emitted to the kernel log.


Ideally we'd have an easy way to actually use them in autoamtic tests.

E.g., loading fails -> error, loading works -> no error (and unload)

> 
> Proposed with permission from Christoph.
> ---
>  lib/Kconfig.debug    |  30 ++++
>  lib/Makefile         |   3 +
>  lib/test_pagealloc.c | 337 ++++++++++++++++++++++++++++++++++++++
>  lib/test_slab.c      | 375 +++++++++++++++++++++++++++++++++++++++++++
>  lib/test_vmstat.c    |  95 +++++++++++
>  5 files changed, 840 insertions(+)
>  create mode 100644 lib/test_pagealloc.c
>  create mode 100644 lib/test_slab.c
>  create mode 100644 lib/test_vmstat.c

This looks similar to tools/testing/selftests/mm/test_vmalloc.sh and friends,
that can actually be executed as part of our selftests and do a modprobe.

Can we have similar scripts to execute them?

But I also wonder if these test modules could be placed then in
tools/testing/selftests/mm/ instead (or some subdirectory for test modules).

Last but not least: MAINTAINERS should cover them.

-- 
Cheers,

David


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

* Re: [patch 1/3] lib: test_vmstat: add synthetic benchmark for vm stats
  2026-07-31  4:26 ` [patch 1/3] lib: test_vmstat: add synthetic benchmark for vm stats David Rientjes
@ 2026-08-01 13:20   ` Usama Arif
  0 siblings, 0 replies; 6+ messages in thread
From: Usama Arif @ 2026-08-01 13:20 UTC (permalink / raw)
  To: David Rientjes
  Cc: Usama Arif, Andrew Morton, David Hildenbrand, Christoph Lameter,
	Vlastimil Babka, Mathieu Desnoyers, linux-mm, linux-kernel

On Thu, 30 Jul 2026 21:26:23 -0700 (PDT) David Rientjes <rientjes@google.com> wrote:

> From: Christoph Lameter <cl@gentwo.org>
> 
> Add a synthetic benchmark that can be used to measure performance of VM
> statistics.  This is used to analyze any improvements or regressions in
> functions that are frequently used in hot code paths.
> 
> The test is run by inserting the module: modprobe test_vmstat
> The module insertion will always fail so that it is automatically
> unloaded; the results are in the kernel log.
> 
> Signed-off-by: Christoph Lameter <cl@gentwo.org>
> Signed-off-by: David Rientjes <rientjes@google.com>
> ---
>  lib/Kconfig.debug | 10 +++++
>  lib/Makefile      |  1 +
>  lib/test_vmstat.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 106 insertions(+)
>  create mode 100644 lib/test_vmstat.c
> 
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index 1244dcac2294..6d743c8e27fa 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -3427,6 +3427,16 @@ config TEST_KEXEC_HANDOVER
>  
>  	  If unsure, say N.
>  
> +config TEST_VMSTAT
> +	tristate "VM Statistics Benchmark"
> +	default n
> +	help
> +	  A synthetic benchmark measuring the performance of VM statistics,
> +	  useful to analyze any improvements or regressions in functions that
> +	  are frequently used in hot code paths.
> +
> +	  If unsure, say N.
> +
>  config RATELIMIT_KUNIT_TEST
>  	tristate "KUnit Test for correctness and stress of ratelimit" if !KUNIT_ALL_TESTS
>  	depends on KUNIT
> diff --git a/lib/Makefile b/lib/Makefile
> index 7f75cc6edf94..5f3ebabd0224 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -105,6 +105,7 @@ obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
>  obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
>  obj-$(CONFIG_TEST_OBJPOOL) += test_objpool.o
>  obj-$(CONFIG_TEST_KEXEC_HANDOVER) += test_kho.o
> +obj-$(CONFIG_TEST_VMSTAT) += test_vmstat.o
>  
>  obj-$(CONFIG_TEST_FPU) += test_fpu.o
>  test_fpu-y := test_fpu_glue.o test_fpu_impl.o
> diff --git a/lib/test_vmstat.c b/lib/test_vmstat.c
> new file mode 100644
> index 000000000000..f63f439ab9d2
> --- /dev/null
> +++ b/lib/test_vmstat.c
> @@ -0,0 +1,95 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Test module for in-kernel synthetic vm statistics performance.
> + *
> + * execute
> + *
> + *	modprobe test_vmstat
> + *
> + * to run this test
> + *
> + * (C) 2009 Linux Foundation, Christoph Lameter <cl@gentwo.org>
> + */
> +
> +#include <linux/jiffies.h>
> +#include <linux/compiler.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/mm.h>
> +#include <asm/timex.h>
> +
> +#define TEST_COUNT 10000
> +
> +static int vmstat_test_init(void)
> +{
> +	unsigned int i;
> +	cycles_t time1, time2, time;
> +	int rem;
> +	struct page *page = alloc_page(GFP_KERNEL);
> +

Good to check page != NULL before using it below.

> +	pr_alert("VMstat testing\n");
> +	pr_alert("=====================\n");
> +	pr_alert("1. inc_zone_page_state() then dec_zone_page_state()\n");
> +	time1 = get_cycles();
> +	for (i = 0; i < TEST_COUNT; i++)
> +		inc_zone_page_state(page, NR_FREE_CMA_PAGES);
> +
> +	time2 = get_cycles();
> +	time = time2 - time1;
> +
> +	pr_alert("%i times inc_zone_page_state() ", i);
> +	time = div_u64_rem(time, TEST_COUNT, &rem);
> +	pr_cont("-> %llu cycles ", (unsigned long long) time);
> +
> +	time1 = get_cycles();
> +	for (i = 0; i < TEST_COUNT; i++)
> +		__dec_zone_page_state(page, NR_FREE_CMA_PAGES);

Why use inc_zone_page_state() but the __ variant for decrement?

Checking the 2 functions, __dec_zone_page_state() might cause
problems if preemption is enabled?

> +
> +	time2 = get_cycles();
> +	time = time2 - time1;
> +
> +	pr_cont("__dec_z_p_s() ");
> +	time = div_u64_rem(time, TEST_COUNT, &rem);
> +	pr_cont("-> %llu cycles\n",  (unsigned long long) time);
> +
> +	pr_alert("2. inc_zone_page_state()/dec_zone_page_state()\n");
> +	time1 = get_cycles();
> +	for (i = 0; i < TEST_COUNT; i++) {
> +		inc_zone_page_state(page, NR_FREE_CMA_PAGES);
> +		dec_zone_page_state(page, NR_FREE_CMA_PAGES);
> +	}
> +
> +	time2 = get_cycles();
> +	time = time2 - time1;
> +
> +	pr_alert("%i times inc/dec ", i);
> +	time = div_u64_rem(time, TEST_COUNT, &rem);
> +	pr_cont("-> %llu cycles\n",  (unsigned long long) time);
> +
> +	pr_alert("3. count_vm_event()\n");
> +	time1 = get_cycles();
> +	for (i = 0; i < TEST_COUNT; i++)
> +		count_vm_event(SLABS_SCANNED);
> +
> +	time2 = get_cycles();
> +	time = time2 - time1;
> +
> +	count_vm_events(SLABS_SCANNED, -TEST_COUNT);
> +	pr_alert("%i count_vm_events ", i);
> +	time = div_u64_rem(time, TEST_COUNT, &rem);
> +	pr_cont("-> %llu cycles\n",  (unsigned long long) time);
> +	__free_page(page);
> +	return -EAGAIN; /* Fail will directly unload the module */
> +}
> +
> +static void vmstat_test_exit(void)
> +{
> +	pr_alert("test exit\n");
> +}
> +
> +module_init(vmstat_test_init)
> +module_exit(vmstat_test_exit)
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Christoph Lameter");
> +MODULE_DESCRIPTION("VM statistics test");
> 
> 


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

end of thread, other threads:[~2026-08-01 13:20 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31  4:26 [patch 0/3] lib: add synthetic MM benchmarks David Rientjes
2026-07-31  4:26 ` [patch 1/3] lib: test_vmstat: add synthetic benchmark for vm stats David Rientjes
2026-08-01 13:20   ` Usama Arif
2026-07-31  4:26 ` [patch 2/3] lib: test_slab: add synthetic benchmark for slab David Rientjes
2026-07-31  4:27 ` [patch 3/3] lib: test_pagealloc: add synthetic benchmark for page allocator David Rientjes
2026-07-31  9:00 ` [patch 0/3] lib: add synthetic MM benchmarks David Hildenbrand (Arm)

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.