]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
mm/damon: replace damon_rand() with a per-ctx lockless PRNG
authorJiayuan Chen <jiayuan.chen@shopee.com>
Sat, 9 May 2026 01:18:15 +0000 (18:18 -0700)
committerAndrew Morton <akpm@linux-foundation.org>
Tue, 2 Jun 2026 22:22:09 +0000 (15:22 -0700)
damon_rand() on the sampling_addr hot path called get_random_u32_below(),
which takes a local_lock_irqsave() around a per-CPU batched entropy pool
and periodically refills it with ChaCha20.  At elevated nr_regions counts
(20k+), the lock_acquire / local_lock pair plus __get_random_u32_below()
dominate kdamond perf profiles.

Replace the helper with a lockless lfsr113 generator (struct rnd_state)
held per damon_ctx and seeded from get_random_u64() in damon_new_ctx().
kdamond is the single consumer of a given ctx, so no synchronization is
required.  Range mapping uses traditional reciprocal multiplication,
similar as get_random_u32_below(); for spans larger than U32_MAX (only
reachable on 64-bit) the slow path combines two u32 outputs and uses
mul_u64_u64_shr() at 64-bit width.  On 32-bit the slow path is dead code
and gets eliminated by the compiler.

The new helper takes a ctx parameter; damon_split_regions_of() and the
kunit tests that call it directly are updated accordingly.

lfsr113 is a linear PRNG and MUST NOT be used for anything
security-sensitive.  DAMON's sampling_addr is not exposed to userspace and
is only consumed as a probe point for PTE accessed-bit sampling, so a
non-cryptographic PRNG is appropriate here.

Tested with paddr monitoring and max_nr_regions=20000: kdamond CPU usage
reduced from ~72% to ~50% of one core.

Link: https://lore.kernel.org/20260505145212.108644-1-jiayuan.chen@linux.dev
Link: https://lore.kernel.org/damon/20260426173346.86238-1-sj@kernel.org/T/#m4f1fd74112728f83a41511e394e8c3fef703039c
Link: https://lore.kernel.org/20260509011816.85145-1-sj@kernel.org
Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
Signed-off-by: SeongJae Park <sj@kernel.org>
Reviewed-by: SeongJae Park <sj@kernel.org>
Cc: Shu Anzai <shu17az@gmail.com>
Cc: Quanmin Yan <yanquanmin1@huawei.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
include/linux/damon.h
mm/damon/core.c
mm/damon/paddr.c
mm/damon/tests/core-kunit.h
mm/damon/vaddr.c

index c7a31572689be8109ae5c2375ffaf86c2776f78c..4d4f031bcb4535fda91dae10a5b4bd6f1984749c 100644 (file)
@@ -8,23 +8,18 @@
 #ifndef _DAMON_H_
 #define _DAMON_H_
 
+#include <linux/math64.h>
 #include <linux/memcontrol.h>
 #include <linux/mutex.h>
+#include <linux/prandom.h>
 #include <linux/time64.h>
 #include <linux/types.h>
-#include <linux/random.h>
 
 /* Minimal region size.  Every damon_region is aligned by this. */
 #define DAMON_MIN_REGION_SZ    PAGE_SIZE
 /* Max priority score for DAMON-based operation schemes */
 #define DAMOS_MAX_SCORE                (99)
 
-/* Get a random number in [l, r) */
-static inline unsigned long damon_rand(unsigned long l, unsigned long r)
-{
-       return l + get_random_u32_below(r - l);
-}
-
 /**
  * struct damon_addr_range - Represents an address region of [@start, @end).
  * @start:     Start address of the region (inclusive).
@@ -859,8 +854,27 @@ struct damon_ctx {
 
        struct list_head adaptive_targets;
        struct list_head schemes;
+
+       /* Per-ctx PRNG state for damon_rand(); kdamond is the sole consumer. */
+       struct rnd_state rnd_state;
 };
 
+/* Get a random number in [@l, @r) using @ctx's lockless PRNG. */
+static inline unsigned long damon_rand(struct damon_ctx *ctx,
+                                      unsigned long l, unsigned long r)
+{
+       unsigned long span = r - l;
+       u64 rnd;
+
+       if (span <= U32_MAX) {
+               rnd = prandom_u32_state(&ctx->rnd_state);
+               return l + (unsigned long)((rnd * span) >> 32);
+       }
+       rnd = ((u64)prandom_u32_state(&ctx->rnd_state) << 32) |
+             prandom_u32_state(&ctx->rnd_state);
+       return l + mul_u64_u64_shr(rnd, span, 64);
+}
+
 static inline struct damon_region *damon_next_region(struct damon_region *r)
 {
        return container_of(r->list.next, struct damon_region, list);
index 9f38deddcb30ecab520d2d076e5182dc963a2c17..3a8725e400c6b89572cc387ad854e34ce26f66d6 100644 (file)
@@ -611,6 +611,8 @@ struct damon_ctx *damon_new_ctx(void)
        INIT_LIST_HEAD(&ctx->adaptive_targets);
        INIT_LIST_HEAD(&ctx->schemes);
 
+       prandom_seed_state(&ctx->rnd_state, get_random_u64());
+
        return ctx;
 }
 
@@ -2939,8 +2941,9 @@ static void damon_split_region_at(struct damon_target *t,
 }
 
 /* Split every region in the given target into 'nr_subs' regions */
-static void damon_split_regions_of(struct damon_target *t, int nr_subs,
-                                 unsigned long min_region_sz)
+static void damon_split_regions_of(struct damon_ctx *ctx,
+                                  struct damon_target *t, int nr_subs,
+                                  unsigned long min_region_sz)
 {
        struct damon_region *r, *next;
        unsigned long sz_region, sz_sub = 0;
@@ -2955,7 +2958,7 @@ static void damon_split_regions_of(struct damon_target *t, int nr_subs,
                         * Randomly select size of left sub-region to be at
                         * least 10 percent and at most 90% of original region
                         */
-                       sz_sub = ALIGN_DOWN(damon_rand(1, 10) *
+                       sz_sub = ALIGN_DOWN(damon_rand(ctx, 1, 10) *
                                        sz_region / 10, min_region_sz);
                        /* Do not allow blank region */
                        if (sz_sub == 0 || sz_sub >= sz_region)
@@ -2996,7 +2999,8 @@ static void kdamond_split_regions(struct damon_ctx *ctx)
                nr_subregions = 3;
 
        damon_for_each_target(t, ctx)
-               damon_split_regions_of(t, nr_subregions, ctx->min_region_sz);
+               damon_split_regions_of(ctx, t, nr_subregions,
+                                      ctx->min_region_sz);
 
        last_nr_regions = nr_regions;
 }
index 5cdcc5037cbc1efc767c1befabcbfe24f25970f7..c4738cd5e221eda81f4d5b41f6e43dcc0fea9a6c 100644 (file)
@@ -49,11 +49,11 @@ static void damon_pa_mkold(phys_addr_t paddr)
 }
 
 static void __damon_pa_prepare_access_check(struct damon_region *r,
-               unsigned long addr_unit)
+               struct damon_ctx *ctx)
 {
-       r->sampling_addr = damon_rand(r->ar.start, r->ar.end);
+       r->sampling_addr = damon_rand(ctx, r->ar.start, r->ar.end);
 
-       damon_pa_mkold(damon_pa_phys_addr(r->sampling_addr, addr_unit));
+       damon_pa_mkold(damon_pa_phys_addr(r->sampling_addr, ctx->addr_unit));
 }
 
 static void damon_pa_prepare_access_checks(struct damon_ctx *ctx)
@@ -63,7 +63,7 @@ static void damon_pa_prepare_access_checks(struct damon_ctx *ctx)
 
        damon_for_each_target(t, ctx) {
                damon_for_each_region(r, t)
-                       __damon_pa_prepare_access_check(r, ctx->addr_unit);
+                       __damon_pa_prepare_access_check(r, ctx);
        }
 }
 
index 1b23a22ac04c4da455ee45f64e8b451a46137be6..866f716e5760db055df51333e7e44e33d44ff30d 100644 (file)
@@ -273,54 +273,70 @@ static void damon_test_merge_regions_of(struct kunit *test)
 
 static void damon_test_split_regions_of(struct kunit *test)
 {
+       struct damon_ctx *c;
        struct damon_target *t;
        struct damon_region *r;
        unsigned long sa[] = {0, 300, 500};
        unsigned long ea[] = {220, 400, 700};
        int i;
 
+       c = damon_new_ctx();
+       if (!c)
+               kunit_skip(test, "ctx alloc fail");
+
        t = damon_new_target();
-       if (!t)
+       if (!t) {
+               damon_destroy_ctx(c);
                kunit_skip(test, "target alloc fail");
+       }
        r = damon_new_region(0, 22);
        if (!r) {
                damon_free_target(t);
+               damon_destroy_ctx(c);
                kunit_skip(test, "region alloc fail");
        }
        damon_add_region(r, t);
-       damon_split_regions_of(t, 2, 1);
+       damon_split_regions_of(c, t, 2, 1);
        KUNIT_EXPECT_LE(test, damon_nr_regions(t), 2u);
        damon_free_target(t);
 
        t = damon_new_target();
-       if (!t)
+       if (!t) {
+               damon_destroy_ctx(c);
                kunit_skip(test, "second target alloc fail");
+       }
        r = damon_new_region(0, 220);
        if (!r) {
                damon_free_target(t);
+               damon_destroy_ctx(c);
                kunit_skip(test, "second region alloc fail");
        }
        damon_add_region(r, t);
-       damon_split_regions_of(t, 4, 1);
+       damon_split_regions_of(c, t, 4, 1);
        KUNIT_EXPECT_LE(test, damon_nr_regions(t), 4u);
        damon_free_target(t);
 
        t = damon_new_target();
-       if (!t)
+       if (!t) {
+               damon_destroy_ctx(c);
                kunit_skip(test, "third target alloc fail");
+       }
        for (i = 0; i < ARRAY_SIZE(sa); i++) {
                r = damon_new_region(sa[i], ea[i]);
                if (!r) {
                        damon_free_target(t);
+                       damon_destroy_ctx(c);
                        kunit_skip(test, "region alloc fail");
                }
                damon_add_region(r, t);
        }
-       damon_split_regions_of(t, 4, 5);
+       damon_split_regions_of(c, t, 4, 5);
        KUNIT_EXPECT_LE(test, damon_nr_regions(t), 12u);
        damon_for_each_region(r, t)
                KUNIT_EXPECT_GE(test, damon_sz_region(r) % 5ul, 0ul);
        damon_free_target(t);
+
+       damon_destroy_ctx(c);
 }
 
 static void damon_test_ops_registration(struct kunit *test)
index dd5f2d7027ac46346ca4e0582509e7c158b8b459..1b0ebe3b6951e84d046ca0a48f2800ad210e23ac 100644 (file)
@@ -333,9 +333,10 @@ static void damon_va_mkold(struct mm_struct *mm, unsigned long addr)
  */
 
 static void __damon_va_prepare_access_check(struct mm_struct *mm,
-                                       struct damon_region *r)
+                                       struct damon_region *r,
+                                       struct damon_ctx *ctx)
 {
-       r->sampling_addr = damon_rand(r->ar.start, r->ar.end);
+       r->sampling_addr = damon_rand(ctx, r->ar.start, r->ar.end);
 
        damon_va_mkold(mm, r->sampling_addr);
 }
@@ -351,7 +352,7 @@ static void damon_va_prepare_access_checks(struct damon_ctx *ctx)
                if (!mm)
                        continue;
                damon_for_each_region(r, t)
-                       __damon_va_prepare_access_check(mm, r);
+                       __damon_va_prepare_access_check(mm, r, ctx);
                mmput(mm);
        }
 }