* [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats
@ 2026-09-18 20:39 David Rientjes
2026-09-26 21:48 ` David Rientjes
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: David Rientjes @ 2026-09-18 20:39 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand (Arm)
Cc: Christoph Lameter, Vlastimil Babka, Mathieu Desnoyers, linux-mm,
linux-kernel, Sarthak Sharma
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 KUnit or doing modprobe vmstat_kunit directly.
Sample output:
KTAP version 1
1..1
KTAP version 1
# Subtest: vmstat
# module: vmstat_kunit
1..3
# vmstat_test_inc_dec_zone_page_state: 10000 ops: inc_zone_page_state -> 8 cycles (4 ns/op), dec_zone_page_state -> 9 cycles (4 ns/op)
ok 1 vmstat_test_inc_dec_zone_page_state
# vmstat_test_interleaved_zone_page_state: 10000 ops: inc/dec pair -> 17 cycles (8 ns/op)
ok 2 vmstat_test_interleaved_zone_page_state
# vmstat_test_count_vm_event: 10000 ops: count_vm_event -> 4 cycles (2 ns/op)
ok 3 vmstat_test_count_vm_event
# vmstat: pass:3 fail:0 skip:0 total:3
# Totals: pass:3 fail:0 skip:0 total:3
ok 1 vmstat
Assisted-by: Gemini:gemini-3.8-flash
Signed-off-by: Christoph Lameter <cl@gentwo.org>
Signed-off-by: David Rientjes <rientjes@google.com>
---
v4:
- included sample output in the commit description
- switched to NR_MLOCK which is display only so no side effects
- remove unnecessary "rem" variable
Once we're happy with this change, I'll apply the same treatment to the
proposed pgalloc and slab tests.
MAINTAINERS | 1 +
mm/Kconfig | 11 +++
mm/Makefile | 1 +
mm/tests/vmstat_kunit.c | 157 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 170 insertions(+)
create mode 100644 mm/tests/vmstat_kunit.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 3b2eb2a7a89a..de09076d91b9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17161,6 +17161,7 @@ F: mm/ptdump.c
F: mm/sparse-vmemmap.c
F: mm/sparse.c
F: mm/sparse.h
+F: mm/tests/vmstat_kunit.c
F: mm/util.c
F: mm/vmpressure.c
F: mm/vmstat.c
diff --git a/mm/Kconfig b/mm/Kconfig
index 604c58199acb..2d021fb5ace6 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -1511,6 +1511,17 @@ config LAZY_MMU_MODE_KUNIT_TEST
If unsure, say N.
+config VMSTAT_KUNIT_TEST
+ tristate "KUnit test for VM statistics" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ Enable this option to test and benchmark the performance of VM
+ statistics updates (zone page state and VM event counters), which
+ are used frequently in hot memory management code paths.
+
+ If unsure, say N.
+
source "mm/damon/Kconfig"
endmenu
diff --git a/mm/Makefile b/mm/Makefile
index e7245cb88c66..3d4f2c43b8d3 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -147,4 +147,5 @@ obj-$(CONFIG_SHRINKER_DEBUG) += shrinker_debug.o
obj-$(CONFIG_EXECMEM) += execmem.o
obj-$(CONFIG_TMPFS_QUOTA) += shmem_quota.o
obj-$(CONFIG_LAZY_MMU_MODE_KUNIT_TEST) += tests/lazy_mmu_mode_kunit.o
+obj-$(CONFIG_VMSTAT_KUNIT_TEST) += tests/vmstat_kunit.o
obj-$(CONFIG_MEM_ALLOC_PROFILING) += alloc_tag.o
diff --git a/mm/tests/vmstat_kunit.c b/mm/tests/vmstat_kunit.c
new file mode 100644
index 000000000000..899c957091d1
--- /dev/null
+++ b/mm/tests/vmstat_kunit.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit synthetic performance benchmark for VM statistics.
+ *
+ * (C) 2009 Linux Foundation, Christoph Lameter <cl@gentwo.org>
+ * (C) 2026 Google LLC, David Rientjes <rientjes@google.com>
+ */
+#include <kunit/test.h>
+#include <linux/mm.h>
+#include <linux/vmstat.h>
+#include <linux/timex.h>
+#include <linux/ktime.h>
+#include <linux/math64.h>
+
+#define TEST_COUNT 10000
+
+static void vmstat_test_free_page(void *arg)
+{
+ __free_page((struct page *)arg);
+}
+
+/*
+ * Test 1: Sequential inc_zone_page_state() followed by dec_zone_page_state().
+ * Net change to zone counters is 0.
+ */
+static void vmstat_test_inc_dec_zone_page_state(struct kunit *test)
+{
+ struct page *page;
+ cycles_t time1, time2, time;
+ u64 t1_ns, t2_ns;
+ u64 inc_cycles, dec_cycles;
+ u64 inc_ns, dec_ns;
+ unsigned int i;
+
+ page = alloc_page(GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, page);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, vmstat_test_free_page, page), 0);
+
+ /* Benchmark inc_zone_page_state() */
+ time1 = get_cycles();
+ t1_ns = ktime_get_ns();
+ for (i = 0; i < TEST_COUNT; i++)
+ inc_zone_page_state(page, NR_MLOCK);
+ t2_ns = ktime_get_ns();
+ time2 = get_cycles();
+
+ time = time2 - time1;
+ inc_cycles = div_u64(time, TEST_COUNT);
+ inc_ns = div_u64(t2_ns - t1_ns, TEST_COUNT);
+
+ /* Benchmark dec_zone_page_state() */
+ time1 = get_cycles();
+ t1_ns = ktime_get_ns();
+ for (i = 0; i < TEST_COUNT; i++)
+ dec_zone_page_state(page, NR_MLOCK);
+ t2_ns = ktime_get_ns();
+ time2 = get_cycles();
+
+ time = time2 - time1;
+ dec_cycles = div_u64(time, TEST_COUNT);
+ dec_ns = div_u64(t2_ns - t1_ns, TEST_COUNT);
+
+ if (inc_cycles || dec_cycles)
+ kunit_info(test, "%u ops: inc_zone_page_state -> %llu cycles (%llu ns/op), dec_zone_page_state -> %llu cycles (%llu ns/op)\n",
+ TEST_COUNT, inc_cycles, inc_ns, dec_cycles, dec_ns);
+ else
+ kunit_info(test, "%u ops: inc_zone_page_state -> %llu ns/op, dec_zone_page_state -> %llu ns/op\n",
+ TEST_COUNT, inc_ns, dec_ns);
+}
+
+/*
+ * Test 2: Paired inc_zone_page_state() and dec_zone_page_state().
+ * Net change to zone counters is 0.
+ */
+static void vmstat_test_interleaved_zone_page_state(struct kunit *test)
+{
+ struct page *page;
+ cycles_t time1, time2, time;
+ u64 t1_ns, t2_ns;
+ u64 avg_cycles, avg_ns;
+ unsigned int i;
+
+ page = alloc_page(GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, page);
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, vmstat_test_free_page, page), 0);
+
+ time1 = get_cycles();
+ t1_ns = ktime_get_ns();
+ for (i = 0; i < TEST_COUNT; i++) {
+ inc_zone_page_state(page, NR_MLOCK);
+ dec_zone_page_state(page, NR_MLOCK);
+ }
+ t2_ns = ktime_get_ns();
+ time2 = get_cycles();
+
+ time = time2 - time1;
+ avg_cycles = div_u64(time, TEST_COUNT);
+ avg_ns = div_u64(t2_ns - t1_ns, TEST_COUNT);
+
+ if (avg_cycles)
+ kunit_info(test, "%u ops: inc/dec pair -> %llu cycles (%llu ns/op)\n",
+ TEST_COUNT, avg_cycles, avg_ns);
+ else
+ kunit_info(test, "%u ops: inc/dec pair -> %llu ns/op\n",
+ TEST_COUNT, avg_ns);
+}
+
+/*
+ * Test 3: count_vm_event() benchmark.
+ * Restores counter balance with count_vm_events(item, -TEST_COUNT).
+ */
+static void vmstat_test_count_vm_event(struct kunit *test)
+{
+ cycles_t time1, time2, time;
+ u64 t1_ns, t2_ns;
+ u64 avg_cycles, avg_ns;
+ unsigned int i;
+
+ time1 = get_cycles();
+ t1_ns = ktime_get_ns();
+ for (i = 0; i < TEST_COUNT; i++)
+ count_vm_event(SLABS_SCANNED);
+ t2_ns = ktime_get_ns();
+ time2 = get_cycles();
+
+ /* Restore balance */
+ count_vm_events(SLABS_SCANNED, -TEST_COUNT);
+
+ time = time2 - time1;
+ avg_cycles = div_u64(time, TEST_COUNT);
+ avg_ns = div_u64(t2_ns - t1_ns, TEST_COUNT);
+
+ if (avg_cycles)
+ kunit_info(test, "%u ops: count_vm_event -> %llu cycles (%llu ns/op)\n",
+ TEST_COUNT, avg_cycles, avg_ns);
+ else
+ kunit_info(test, "%u ops: count_vm_event -> %llu ns/op\n",
+ TEST_COUNT, avg_ns);
+}
+
+static struct kunit_case vmstat_test_cases[] = {
+ KUNIT_CASE(vmstat_test_inc_dec_zone_page_state),
+ KUNIT_CASE(vmstat_test_interleaved_zone_page_state),
+ KUNIT_CASE(vmstat_test_count_vm_event),
+ {}
+};
+
+static struct kunit_suite vmstat_test_suite = {
+ .name = "vmstat",
+ .test_cases = vmstat_test_cases,
+};
+kunit_test_suite(vmstat_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Christoph Lameter <cl@gentwo.org>");
+MODULE_AUTHOR("David Rientjes <rientjes@google.com>");
+MODULE_DESCRIPTION("KUnit benchmark test for VM statistics");
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats
2026-09-18 20:39 [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats David Rientjes
@ 2026-09-26 21:48 ` David Rientjes
2026-09-26 22:38 ` Andrew Morton
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: David Rientjes @ 2026-09-26 21:48 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand (Arm)
Cc: Christoph Lameter, Vlastimil Babka, Mathieu Desnoyers, linux-mm,
linux-kernel, Sarthak Sharma
On Fri, 18 Sep 2026, David Rientjes 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 KUnit or doing modprobe vmstat_kunit directly.
>
> Sample output:
> KTAP version 1
> 1..1
> KTAP version 1
> # Subtest: vmstat
> # module: vmstat_kunit
> 1..3
> # vmstat_test_inc_dec_zone_page_state: 10000 ops: inc_zone_page_state -> 8 cycles (4 ns/op), dec_zone_page_state -> 9 cycles (4 ns/op)
> ok 1 vmstat_test_inc_dec_zone_page_state
> # vmstat_test_interleaved_zone_page_state: 10000 ops: inc/dec pair -> 17 cycles (8 ns/op)
> ok 2 vmstat_test_interleaved_zone_page_state
> # vmstat_test_count_vm_event: 10000 ops: count_vm_event -> 4 cycles (2 ns/op)
> ok 3 vmstat_test_count_vm_event
> # vmstat: pass:3 fail:0 skip:0 total:3
> # Totals: pass:3 fail:0 skip:0 total:3
> ok 1 vmstat
>
> Assisted-by: Gemini:gemini-3.8-flash
> Signed-off-by: Christoph Lameter <cl@gentwo.org>
> Signed-off-by: David Rientjes <rientjes@google.com>
> ---
> v4:
> - included sample output in the commit description
> - switched to NR_MLOCK which is display only so no side effects
> - remove unnecessary "rem" variable
>
> Once we're happy with this change, I'll apply the same treatment to the
> proposed pgalloc and slab tests.
>
Andrew, let me know if there is any additional feedback for this one and
I'd be happy to integrate it. I've got the other two test cases converted
over to kunit tests as well per the feedback so will send this out as soon
as we're happy with this one. Thanks!
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats
2026-09-18 20:39 [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats David Rientjes
2026-09-26 21:48 ` David Rientjes
@ 2026-09-26 22:38 ` Andrew Morton
2026-09-28 12:19 ` David Hildenbrand (Arm)
2026-09-29 4:23 ` Anshuman Khandual
3 siblings, 0 replies; 6+ messages in thread
From: Andrew Morton @ 2026-09-26 22:38 UTC (permalink / raw)
To: David Rientjes
Cc: David Hildenbrand (Arm),
Christoph Lameter, Vlastimil Babka, Mathieu Desnoyers, linux-mm,
linux-kernel, Sarthak Sharma
On Fri, 18 Sep 2026 13:39:52 -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 KUnit or doing modprobe vmstat_kunit directly.
>
> Sample output:
> KTAP version 1
> 1..1
> KTAP version 1
> # Subtest: vmstat
> # module: vmstat_kunit
> 1..3
> # vmstat_test_inc_dec_zone_page_state: 10000 ops: inc_zone_page_state -> 8 cycles (4 ns/op), dec_zone_page_state -> 9 cycles (4 ns/op)
> ok 1 vmstat_test_inc_dec_zone_page_state
> # vmstat_test_interleaved_zone_page_state: 10000 ops: inc/dec pair -> 17 cycles (8 ns/op)
> ok 2 vmstat_test_interleaved_zone_page_state
> # vmstat_test_count_vm_event: 10000 ops: count_vm_event -> 4 cycles (2 ns/op)
> ok 3 vmstat_test_count_vm_event
> # vmstat: pass:3 fail:0 skip:0 total:3
> # Totals: pass:3 fail:0 skip:0 total:3
> ok 1 vmstat
This hasn't generated a lot of interest.
Can you better explain the value? How do you anticipate that people
use this? What value does it offer? Sell it to us!
People could use it across kernel versions to detect performance
regressions/improvements. "kernel test robot" appears to already be
set up to track this (eg,
https://lore.kernel.org/202609230824.7a9189aa-lkp@intel.com). Do you
anticipate that this module will be wired into such projects?
Other than that, it seems it's mainly a developer-centric thing? An
individual developer could use it to verify that a proposed vmstat
change doesn't slow things down, or that it does speed things up, if
that was the intent.
Will google use this internally, or will they do so? If so, how?
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats
2026-09-18 20:39 [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats David Rientjes
2026-09-26 21:48 ` David Rientjes
2026-09-26 22:38 ` Andrew Morton
@ 2026-09-28 12:19 ` David Hildenbrand (Arm)
2026-10-03 0:50 ` David Rientjes
2026-09-29 4:23 ` Anshuman Khandual
3 siblings, 1 reply; 6+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-28 12:19 UTC (permalink / raw)
To: David Rientjes, Andrew Morton
Cc: Christoph Lameter, Vlastimil Babka, Mathieu Desnoyers, linux-mm,
linux-kernel, Sarthak Sharma
On 9/18/26 22:39, David Rientjes 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 KUnit or doing modprobe vmstat_kunit directly.
>
> Sample output:
> KTAP version 1
> 1..1
> KTAP version 1
> # Subtest: vmstat
> # module: vmstat_kunit
> 1..3
> # vmstat_test_inc_dec_zone_page_state: 10000 ops: inc_zone_page_state -> 8 cycles (4 ns/op), dec_zone_page_state -> 9 cycles (4 ns/op)
> ok 1 vmstat_test_inc_dec_zone_page_state
> # vmstat_test_interleaved_zone_page_state: 10000 ops: inc/dec pair -> 17 cycles (8 ns/op)
> ok 2 vmstat_test_interleaved_zone_page_state
> # vmstat_test_count_vm_event: 10000 ops: count_vm_event -> 4 cycles (2 ns/op)
> ok 3 vmstat_test_count_vm_event
> # vmstat: pass:3 fail:0 skip:0 total:3
> # Totals: pass:3 fail:0 skip:0 total:3
> ok 1 vmstat
>
> Assisted-by: Gemini:gemini-3.8-flash
> Signed-off-by: Christoph Lameter <cl@gentwo.org>
> Signed-off-by: David Rientjes <rientjes@google.com>
> ---
> v4:
> - included sample output in the commit description
> - switched to NR_MLOCK which is display only so no side effects
> - remove unnecessary "rem" variable
>
> Once we're happy with this change, I'll apply the same treatment to the
> proposed pgalloc and slab tests.
>
> MAINTAINERS | 1 +
> mm/Kconfig | 11 +++
> mm/Makefile | 1 +
> mm/tests/vmstat_kunit.c | 157 ++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 170 insertions(+)
> create mode 100644 mm/tests/vmstat_kunit.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3b2eb2a7a89a..de09076d91b9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17161,6 +17161,7 @@ F: mm/ptdump.c
> F: mm/sparse-vmemmap.c
> F: mm/sparse.c
> F: mm/sparse.h
> +F: mm/tests/vmstat_kunit.c
> F: mm/util.c
> F: mm/vmpressure.c
> F: mm/vmstat.c
> diff --git a/mm/Kconfig b/mm/Kconfig
> index 604c58199acb..2d021fb5ace6 100644
> --- a/mm/Kconfig
> +++ b/mm/Kconfig
> @@ -1511,6 +1511,17 @@ config LAZY_MMU_MODE_KUNIT_TEST
>
> If unsure, say N.
>
> +config VMSTAT_KUNIT_TEST
> + tristate "KUnit test for VM statistics" if !KUNIT_ALL_TESTS
> + depends on KUNIT
> + default KUNIT_ALL_TESTS
> + help
> + Enable this option to test and benchmark the performance of VM
> + statistics updates (zone page state and VM event counters), which
> + are used frequently in hot memory management code paths.
> +
> + If unsure, say N.
> +
> source "mm/damon/Kconfig"
>
> endmenu
> diff --git a/mm/Makefile b/mm/Makefile
> index e7245cb88c66..3d4f2c43b8d3 100644
> --- a/mm/Makefile
> +++ b/mm/Makefile
> @@ -147,4 +147,5 @@ obj-$(CONFIG_SHRINKER_DEBUG) += shrinker_debug.o
> obj-$(CONFIG_EXECMEM) += execmem.o
> obj-$(CONFIG_TMPFS_QUOTA) += shmem_quota.o
> obj-$(CONFIG_LAZY_MMU_MODE_KUNIT_TEST) += tests/lazy_mmu_mode_kunit.o
> +obj-$(CONFIG_VMSTAT_KUNIT_TEST) += tests/vmstat_kunit.o
> obj-$(CONFIG_MEM_ALLOC_PROFILING) += alloc_tag.o
> diff --git a/mm/tests/vmstat_kunit.c b/mm/tests/vmstat_kunit.c
> new file mode 100644
> index 000000000000..899c957091d1
> --- /dev/null
> +++ b/mm/tests/vmstat_kunit.c
> @@ -0,0 +1,157 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * KUnit synthetic performance benchmark for VM statistics.
> + *
> + * (C) 2009 Linux Foundation, Christoph Lameter <cl@gentwo.org>
> + * (C) 2026 Google LLC, David Rientjes <rientjes@google.com>
> + */
> +#include <kunit/test.h>
> +#include <linux/mm.h>
> +#include <linux/vmstat.h>
> +#include <linux/timex.h>
> +#include <linux/ktime.h>
> +#include <linux/math64.h>
> +
> +#define TEST_COUNT 10000
> +
> +static void vmstat_test_free_page(void *arg)
> +{
> + __free_page((struct page *)arg);
> +}
> +
> +/*
> + * Test 1: Sequential inc_zone_page_state() followed by dec_zone_page_state().
> + * Net change to zone counters is 0.
> + */
> +static void vmstat_test_inc_dec_zone_page_state(struct kunit *test)
> +{
> + struct page *page;
> + cycles_t time1, time2, time;
> + u64 t1_ns, t2_ns;
> + u64 inc_cycles, dec_cycles;
> + u64 inc_ns, dec_ns;
> + unsigned int i;
> +
> + page = alloc_page(GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, page);
> + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, vmstat_test_free_page, page), 0);
> +
> + /* Benchmark inc_zone_page_state() */
> + time1 = get_cycles();
> + t1_ns = ktime_get_ns();
> + for (i = 0; i < TEST_COUNT; i++)
> + inc_zone_page_state(page, NR_MLOCK);
Just curious, could some compiler optimizations here change the picture, and
doing the average would not actually be representative? Not sure if a barrier()
would reliably avoid that.
Maybe not relevant today, but I do wonder if we should take care that it stays
that way.
Applies to all cases below where we loop.
This looks much cleaner to me, thanks!
--
Cheers,
David
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats
2026-09-18 20:39 [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats David Rientjes
` (2 preceding siblings ...)
2026-09-28 12:19 ` David Hildenbrand (Arm)
@ 2026-09-29 4:23 ` Anshuman Khandual
3 siblings, 0 replies; 6+ messages in thread
From: Anshuman Khandual @ 2026-09-29 4:23 UTC (permalink / raw)
To: David Rientjes
Cc: Andrew Morton, David Hildenbrand (Arm),
Christoph Lameter, Vlastimil Babka, Mathieu Desnoyers, linux-mm,
linux-kernel, Sarthak Sharma
On Fri, Sep 18, 2026 at 01:39:52PM -0700, David Rientjes 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 KUnit or doing modprobe vmstat_kunit directly.
>
> Sample output:
> KTAP version 1
> 1..1
> KTAP version 1
> # Subtest: vmstat
> # module: vmstat_kunit
> 1..3
> # vmstat_test_inc_dec_zone_page_state: 10000 ops: inc_zone_page_state -> 8 cycles (4 ns/op), dec_zone_page_state -> 9 cycles (4 ns/op)
> ok 1 vmstat_test_inc_dec_zone_page_state
> # vmstat_test_interleaved_zone_page_state: 10000 ops: inc/dec pair -> 17 cycles (8 ns/op)
> ok 2 vmstat_test_interleaved_zone_page_state
> # vmstat_test_count_vm_event: 10000 ops: count_vm_event -> 4 cycles (2 ns/op)
> ok 3 vmstat_test_count_vm_event
> # vmstat: pass:3 fail:0 skip:0 total:3
> # Totals: pass:3 fail:0 skip:0 total:3
Is not "pass:3" misleading ? KUNIT tests here dump performance numbers,
hence how they could be treated as pass or fail ? What's the objective
for these tests ?
> ok 1 vmstat
>
> Assisted-by: Gemini:gemini-3.8-flash
> Signed-off-by: Christoph Lameter <cl@gentwo.org>
> Signed-off-by: David Rientjes <rientjes@google.com>
> ---
> v4:
> - included sample output in the commit description
> - switched to NR_MLOCK which is display only so no side effects
> - remove unnecessary "rem" variable
>
> Once we're happy with this change, I'll apply the same treatment to the
> proposed pgalloc and slab tests.
>
> MAINTAINERS | 1 +
> mm/Kconfig | 11 +++
> mm/Makefile | 1 +
> mm/tests/vmstat_kunit.c | 157 ++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 170 insertions(+)
> create mode 100644 mm/tests/vmstat_kunit.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3b2eb2a7a89a..de09076d91b9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17161,6 +17161,7 @@ F: mm/ptdump.c
> F: mm/sparse-vmemmap.c
> F: mm/sparse.c
> F: mm/sparse.h
> +F: mm/tests/vmstat_kunit.c
> F: mm/util.c
> F: mm/vmpressure.c
> F: mm/vmstat.c
> diff --git a/mm/Kconfig b/mm/Kconfig
> index 604c58199acb..2d021fb5ace6 100644
> --- a/mm/Kconfig
> +++ b/mm/Kconfig
> @@ -1511,6 +1511,17 @@ config LAZY_MMU_MODE_KUNIT_TEST
>
> If unsure, say N.
>
> +config VMSTAT_KUNIT_TEST
> + tristate "KUnit test for VM statistics" if !KUNIT_ALL_TESTS
> + depends on KUNIT
> + default KUNIT_ALL_TESTS
> + help
> + Enable this option to test and benchmark the performance of VM
> + statistics updates (zone page state and VM event counters), which
> + are used frequently in hot memory management code paths.
> +
> + If unsure, say N.
> +
> source "mm/damon/Kconfig"
>
> endmenu
> diff --git a/mm/Makefile b/mm/Makefile
> index e7245cb88c66..3d4f2c43b8d3 100644
> --- a/mm/Makefile
> +++ b/mm/Makefile
> @@ -147,4 +147,5 @@ obj-$(CONFIG_SHRINKER_DEBUG) += shrinker_debug.o
> obj-$(CONFIG_EXECMEM) += execmem.o
> obj-$(CONFIG_TMPFS_QUOTA) += shmem_quota.o
> obj-$(CONFIG_LAZY_MMU_MODE_KUNIT_TEST) += tests/lazy_mmu_mode_kunit.o
> +obj-$(CONFIG_VMSTAT_KUNIT_TEST) += tests/vmstat_kunit.o
> obj-$(CONFIG_MEM_ALLOC_PROFILING) += alloc_tag.o
> diff --git a/mm/tests/vmstat_kunit.c b/mm/tests/vmstat_kunit.c
> new file mode 100644
> index 000000000000..899c957091d1
> --- /dev/null
> +++ b/mm/tests/vmstat_kunit.c
> @@ -0,0 +1,157 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * KUnit synthetic performance benchmark for VM statistics.
> + *
> + * (C) 2009 Linux Foundation, Christoph Lameter <cl@gentwo.org>
> + * (C) 2026 Google LLC, David Rientjes <rientjes@google.com>
> + */
> +#include <kunit/test.h>
> +#include <linux/mm.h>
> +#include <linux/vmstat.h>
> +#include <linux/timex.h>
> +#include <linux/ktime.h>
> +#include <linux/math64.h>
> +
> +#define TEST_COUNT 10000
> +
> +static void vmstat_test_free_page(void *arg)
> +{
> + __free_page((struct page *)arg);
> +}
> +
> +/*
> + * Test 1: Sequential inc_zone_page_state() followed by dec_zone_page_state().
> + * Net change to zone counters is 0.
> + */
> +static void vmstat_test_inc_dec_zone_page_state(struct kunit *test)
> +{
> + struct page *page;
> + cycles_t time1, time2, time;
> + u64 t1_ns, t2_ns;
> + u64 inc_cycles, dec_cycles;
> + u64 inc_ns, dec_ns;
> + unsigned int i;
> +
> + page = alloc_page(GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, page);
> + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, vmstat_test_free_page, page), 0);
> +
> + /* Benchmark inc_zone_page_state() */
> + time1 = get_cycles();
> + t1_ns = ktime_get_ns();
> + for (i = 0; i < TEST_COUNT; i++)
> + inc_zone_page_state(page, NR_MLOCK);
> + t2_ns = ktime_get_ns();
> + time2 = get_cycles();
get_cycles() will count for both ktime_get_ns() in the above code block.
Is not the goal here to characterize just inc_zone_page_state() ?
> +
> + time = time2 - time1;
> + inc_cycles = div_u64(time, TEST_COUNT);
> + inc_ns = div_u64(t2_ns - t1_ns, TEST_COUNT);
> +
> + /* Benchmark dec_zone_page_state() */
> + time1 = get_cycles();
> + t1_ns = ktime_get_ns();
> + for (i = 0; i < TEST_COUNT; i++)
> + dec_zone_page_state(page, NR_MLOCK);
> + t2_ns = ktime_get_ns();
> + time2 = get_cycles();
> +
> + time = time2 - time1;
> + dec_cycles = div_u64(time, TEST_COUNT);
> + dec_ns = div_u64(t2_ns - t1_ns, TEST_COUNT);
> +
> + if (inc_cycles || dec_cycles)
> + kunit_info(test, "%u ops: inc_zone_page_state -> %llu cycles (%llu ns/op), dec_zone_page_state -> %llu cycles (%llu ns/op)\n",
> + TEST_COUNT, inc_cycles, inc_ns, dec_cycles, dec_ns);
> + else
> + kunit_info(test, "%u ops: inc_zone_page_state -> %llu ns/op, dec_zone_page_state -> %llu ns/op\n",
> + TEST_COUNT, inc_ns, dec_ns);
> +}
> +
> +/*
> + * Test 2: Paired inc_zone_page_state() and dec_zone_page_state().
> + * Net change to zone counters is 0.
> + */
> +static void vmstat_test_interleaved_zone_page_state(struct kunit *test)
> +{
> + struct page *page;
> + cycles_t time1, time2, time;
> + u64 t1_ns, t2_ns;
> + u64 avg_cycles, avg_ns;
> + unsigned int i;
> +
> + page = alloc_page(GFP_KERNEL);
> + KUNIT_ASSERT_NOT_NULL(test, page);
> + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, vmstat_test_free_page, page), 0);
> +
> + time1 = get_cycles();
> + t1_ns = ktime_get_ns();
> + for (i = 0; i < TEST_COUNT; i++) {
> + inc_zone_page_state(page, NR_MLOCK);
> + dec_zone_page_state(page, NR_MLOCK);
> + }
> + t2_ns = ktime_get_ns();
> + time2 = get_cycles();
> +
> + time = time2 - time1;
> + avg_cycles = div_u64(time, TEST_COUNT);
> + avg_ns = div_u64(t2_ns - t1_ns, TEST_COUNT);
> +
> + if (avg_cycles)
> + kunit_info(test, "%u ops: inc/dec pair -> %llu cycles (%llu ns/op)\n",
> + TEST_COUNT, avg_cycles, avg_ns);
> + else
> + kunit_info(test, "%u ops: inc/dec pair -> %llu ns/op\n",
> + TEST_COUNT, avg_ns);
> +}
> +
> +/*
> + * Test 3: count_vm_event() benchmark.
> + * Restores counter balance with count_vm_events(item, -TEST_COUNT).
> + */
> +static void vmstat_test_count_vm_event(struct kunit *test)
> +{
> + cycles_t time1, time2, time;
> + u64 t1_ns, t2_ns;
> + u64 avg_cycles, avg_ns;
> + unsigned int i;
> +
> + time1 = get_cycles();
> + t1_ns = ktime_get_ns();
> + for (i = 0; i < TEST_COUNT; i++)
> + count_vm_event(SLABS_SCANNED);
> + t2_ns = ktime_get_ns();
> + time2 = get_cycles();
> +
> + /* Restore balance */
> + count_vm_events(SLABS_SCANNED, -TEST_COUNT);
> +
> + time = time2 - time1;
> + avg_cycles = div_u64(time, TEST_COUNT);
> + avg_ns = div_u64(t2_ns - t1_ns, TEST_COUNT);
> +
> + if (avg_cycles)
> + kunit_info(test, "%u ops: count_vm_event -> %llu cycles (%llu ns/op)\n",
> + TEST_COUNT, avg_cycles, avg_ns);
> + else
> + kunit_info(test, "%u ops: count_vm_event -> %llu ns/op\n",
> + TEST_COUNT, avg_ns);
> +}
> +
> +static struct kunit_case vmstat_test_cases[] = {
> + KUNIT_CASE(vmstat_test_inc_dec_zone_page_state),
> + KUNIT_CASE(vmstat_test_interleaved_zone_page_state),
> + KUNIT_CASE(vmstat_test_count_vm_event),
> + {}
> +};
> +
> +static struct kunit_suite vmstat_test_suite = {
> + .name = "vmstat",
> + .test_cases = vmstat_test_cases,
> +};
> +kunit_test_suite(vmstat_test_suite);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Christoph Lameter <cl@gentwo.org>");
> +MODULE_AUTHOR("David Rientjes <rientjes@google.com>");
> +MODULE_DESCRIPTION("KUnit benchmark test for VM statistics");
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats
2026-09-28 12:19 ` David Hildenbrand (Arm)
@ 2026-10-03 0:50 ` David Rientjes
0 siblings, 0 replies; 6+ messages in thread
From: David Rientjes @ 2026-10-03 0:50 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Andrew Morton, Christoph Lameter, Vlastimil Babka,
Mathieu Desnoyers, linux-mm, linux-kernel, Sarthak Sharma
On Mon, 28 Sep 2026, David Hildenbrand (Arm) wrote:
> On 9/18/26 22:39, David Rientjes 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 KUnit or doing modprobe vmstat_kunit directly.
> >
> > Sample output:
> > KTAP version 1
> > 1..1
> > KTAP version 1
> > # Subtest: vmstat
> > # module: vmstat_kunit
> > 1..3
> > # vmstat_test_inc_dec_zone_page_state: 10000 ops: inc_zone_page_state -> 8 cycles (4 ns/op), dec_zone_page_state -> 9 cycles (4 ns/op)
> > ok 1 vmstat_test_inc_dec_zone_page_state
> > # vmstat_test_interleaved_zone_page_state: 10000 ops: inc/dec pair -> 17 cycles (8 ns/op)
> > ok 2 vmstat_test_interleaved_zone_page_state
> > # vmstat_test_count_vm_event: 10000 ops: count_vm_event -> 4 cycles (2 ns/op)
> > ok 3 vmstat_test_count_vm_event
> > # vmstat: pass:3 fail:0 skip:0 total:3
> > # Totals: pass:3 fail:0 skip:0 total:3
> > ok 1 vmstat
> >
> > Assisted-by: Gemini:gemini-3.8-flash
> > Signed-off-by: Christoph Lameter <cl@gentwo.org>
> > Signed-off-by: David Rientjes <rientjes@google.com>
> > ---
> > v4:
> > - included sample output in the commit description
> > - switched to NR_MLOCK which is display only so no side effects
> > - remove unnecessary "rem" variable
> >
> > Once we're happy with this change, I'll apply the same treatment to the
> > proposed pgalloc and slab tests.
> >
> > MAINTAINERS | 1 +
> > mm/Kconfig | 11 +++
> > mm/Makefile | 1 +
> > mm/tests/vmstat_kunit.c | 157 ++++++++++++++++++++++++++++++++++++++++
> > 4 files changed, 170 insertions(+)
> > create mode 100644 mm/tests/vmstat_kunit.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 3b2eb2a7a89a..de09076d91b9 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -17161,6 +17161,7 @@ F: mm/ptdump.c
> > F: mm/sparse-vmemmap.c
> > F: mm/sparse.c
> > F: mm/sparse.h
> > +F: mm/tests/vmstat_kunit.c
> > F: mm/util.c
> > F: mm/vmpressure.c
> > F: mm/vmstat.c
> > diff --git a/mm/Kconfig b/mm/Kconfig
> > index 604c58199acb..2d021fb5ace6 100644
> > --- a/mm/Kconfig
> > +++ b/mm/Kconfig
> > @@ -1511,6 +1511,17 @@ config LAZY_MMU_MODE_KUNIT_TEST
> >
> > If unsure, say N.
> >
> > +config VMSTAT_KUNIT_TEST
> > + tristate "KUnit test for VM statistics" if !KUNIT_ALL_TESTS
> > + depends on KUNIT
> > + default KUNIT_ALL_TESTS
> > + help
> > + Enable this option to test and benchmark the performance of VM
> > + statistics updates (zone page state and VM event counters), which
> > + are used frequently in hot memory management code paths.
> > +
> > + If unsure, say N.
> > +
> > source "mm/damon/Kconfig"
> >
> > endmenu
> > diff --git a/mm/Makefile b/mm/Makefile
> > index e7245cb88c66..3d4f2c43b8d3 100644
> > --- a/mm/Makefile
> > +++ b/mm/Makefile
> > @@ -147,4 +147,5 @@ obj-$(CONFIG_SHRINKER_DEBUG) += shrinker_debug.o
> > obj-$(CONFIG_EXECMEM) += execmem.o
> > obj-$(CONFIG_TMPFS_QUOTA) += shmem_quota.o
> > obj-$(CONFIG_LAZY_MMU_MODE_KUNIT_TEST) += tests/lazy_mmu_mode_kunit.o
> > +obj-$(CONFIG_VMSTAT_KUNIT_TEST) += tests/vmstat_kunit.o
> > obj-$(CONFIG_MEM_ALLOC_PROFILING) += alloc_tag.o
> > diff --git a/mm/tests/vmstat_kunit.c b/mm/tests/vmstat_kunit.c
> > new file mode 100644
> > index 000000000000..899c957091d1
> > --- /dev/null
> > +++ b/mm/tests/vmstat_kunit.c
> > @@ -0,0 +1,157 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * KUnit synthetic performance benchmark for VM statistics.
> > + *
> > + * (C) 2009 Linux Foundation, Christoph Lameter <cl@gentwo.org>
> > + * (C) 2026 Google LLC, David Rientjes <rientjes@google.com>
> > + */
> > +#include <kunit/test.h>
> > +#include <linux/mm.h>
> > +#include <linux/vmstat.h>
> > +#include <linux/timex.h>
> > +#include <linux/ktime.h>
> > +#include <linux/math64.h>
> > +
> > +#define TEST_COUNT 10000
> > +
> > +static void vmstat_test_free_page(void *arg)
> > +{
> > + __free_page((struct page *)arg);
> > +}
> > +
> > +/*
> > + * Test 1: Sequential inc_zone_page_state() followed by dec_zone_page_state().
> > + * Net change to zone counters is 0.
> > + */
> > +static void vmstat_test_inc_dec_zone_page_state(struct kunit *test)
> > +{
> > + struct page *page;
> > + cycles_t time1, time2, time;
> > + u64 t1_ns, t2_ns;
> > + u64 inc_cycles, dec_cycles;
> > + u64 inc_ns, dec_ns;
> > + unsigned int i;
> > +
> > + page = alloc_page(GFP_KERNEL);
> > + KUNIT_ASSERT_NOT_NULL(test, page);
> > + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, vmstat_test_free_page, page), 0);
> > +
> > + /* Benchmark inc_zone_page_state() */
> > + time1 = get_cycles();
> > + t1_ns = ktime_get_ns();
> > + for (i = 0; i < TEST_COUNT; i++)
> > + inc_zone_page_state(page, NR_MLOCK);
>
> Just curious, could some compiler optimizations here change the picture, and
> doing the average would not actually be representative? Not sure if a barrier()
> would reliably avoid that.
>
> Maybe not relevant today, but I do wonder if we should take care that it stays
> that way.
>
> Applies to all cases below where we loop.
> This looks much cleaner to me, thanks!
>
Thanks all for the feedback, answering all three emails here.
Andrew's questions are spot on and his assumptions are correct. This is
the first of three tests previously posted that measure the execution
times of critical mm functions. This is the simplest of the tests, the
others are for lots of concurrent allocations through the page allocator
and through the slab allocator.
It's anticipated to be used by developers to measure the impact of any
core changes that would end up causing workload performance issues since
things like vmstats, page allocations, and slab allocations are all hot
paths. It could also be used to detect regressions over time, from
kernel version to kernel version, independent of development use cases.
So it could certainly be wired up to the kernel test robot.
We've carried this internally in our own test suite since Christoph
posted them back in 2009 :) We can build with the tests enabled to
measure the impact of any changes in these areas.
The "pass:3" is an artifact of moving this test to kunit, the tests
themselves will not fail and we're only interested in the ns/op.
DavidH: I don't think the compiler can fold or merge the loop bodies, the
compiler shouldn't be able to see through the call to the exported symbol
and actually needs to call it 10,000 times :) I don't think barrier()
adds anything.
I should make this test depend on CONFIG_VM_EVENT_COUNTERS, however,
otherwise that loop is going to be pretty fast.
If there aren't any concerns, I'll send out a v5 with that change.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-03 0:50 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-18 20:39 [patch v4] mm: vmstat_kunit: add synthetic benchmark for vm stats David Rientjes
2026-09-26 21:48 ` David Rientjes
2026-09-26 22:38 ` Andrew Morton
2026-09-28 12:19 ` David Hildenbrand (Arm)
2026-10-03 0:50 ` David Rientjes
2026-09-29 4:23 ` Anshuman Khandual
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®