[RFC PATCH 4/7] mm/damon/core: flat-array snapshot + bsearch in ring-drain loop

From: Ravi Jonnalagadda

Date: Sat May 16 2026 - 18:36:18 EST


The drain loop is O(reports x regions) when matching each ring entry
back to a region. At sufficiently large CPU x region products the
linear scan exceeds the sample interval, producing unbounded backlog.

At drain start, snapshot each target's regions into a flat array
(struct damon_target_lookup), already sorted by ascending r->ar.start.
Replace the linear lookup with binary search over the snapshot,
reducing the drain to O(reports x log2(regions)).

Reject reports that straddle a region boundary
(addr + report->size > r->ar.end) so partial-region accesses are
not credited to the lower region.

If the snapshot allocation fails under memory pressure, log
ratelimited and fall through to zero-access reporting; the next
tick retries.

Hoist the per-report damon_sample_filter_out() check out of the
per-target loop so it runs once per ring entry rather than N times.

Signed-off-by: Ravi Jonnalagadda <ravis.opensrc@xxxxxxxxx>
---
include/linux/damon.h | 7 +++
mm/damon/core.c | 137 ++++++++++++++++++++++++++++++++++++------
2 files changed, 126 insertions(+), 18 deletions(-)

diff --git a/include/linux/damon.h b/include/linux/damon.h
index 8e6e1cd89e551..35cc3d42fcba8 100644
--- a/include/linux/damon.h
+++ b/include/linux/damon.h
@@ -1045,6 +1045,13 @@ struct damon_ctx {

/* Per-ctx PRNG state for damon_rand(); kdamond is the sole consumer. */
struct rnd_state rnd_state;
+ /* Reusable drain-loop snapshot buffer (avoids per-tick kmalloc) */
+ struct {
+ struct damon_target_lookup *lookups;
+ unsigned int nr_lookups;
+ struct damon_region **region_buf;
+ unsigned int region_buf_cap;
+ } drain_snapshot;
};

/* Get a random number in [@l, @r) using @ctx's lockless PRNG. */
diff --git a/mm/damon/core.c b/mm/damon/core.c
index 9ed789e932ebd..03f9c671e8bc9 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -29,6 +29,13 @@
#define DAMON_REPORT_RING_SIZE 256
#define DAMON_REPORT_RING_MASK (DAMON_REPORT_RING_SIZE - 1)

+/* Per-target region lookup for drain loop */
+struct damon_target_lookup {
+ struct damon_region **regions;
+ unsigned int nr_regions;
+};
+
+
struct damon_report_ring {
unsigned int head; /* written by producer (NMI) */
unsigned int tail /* written by consumer (kdamond) */
@@ -855,6 +862,7 @@ struct damon_ctx *damon_new_ctx(void)
INIT_LIST_HEAD(&ctx->schemes);

prandom_seed_state(&ctx->rnd_state, get_random_u64());
+ /* drain_snapshot zero-initialized by kzalloc — no explicit init */

return ctx;
}
@@ -884,6 +892,8 @@ void damon_destroy_ctx(struct damon_ctx *ctx)
damon_for_each_sample_filter_safe(f, next_f, &ctx->sample_control)
damon_destroy_sample_filter(f, &ctx->sample_control);

+ kfree(ctx->drain_snapshot.lookups);
+ kfree(ctx->drain_snapshot.region_buf);
module_put(ctx->ops.owner);
kfree(ctx);
}
@@ -3806,27 +3816,44 @@ static bool damon_sample_filter_out(struct damon_access_report *report,
return !filter->allow;
}

+
static void kdamond_apply_access_report(struct damon_access_report *report,
- struct damon_target *t, struct damon_ctx *ctx)
+ struct damon_region **regions, unsigned int nr_regions,
+ struct damon_ctx *ctx)
{
struct damon_region *r;
unsigned long addr;
+ int left, right, mid;

- if (damon_sample_filter_out(report, &ctx->sample_control))
- return;
if (damon_target_has_pid(ctx))
addr = report->vaddr;
else
addr = report->paddr;

- /* todo: make search faster, e.g., binary search? */
- damon_for_each_region(r, t) {
- if (addr < r->ar.start)
- continue;
- if (r->ar.end < addr + report->size)
- continue;
- if (!r->access_reported)
- damon_update_region_access_rate(r, true, &ctx->attrs);
+ /* Binary search the snapshot for the region containing addr. */
+ left = 0;
+ right = nr_regions - 1;
+ r = NULL;
+ while (left <= right) {
+ /* Avoid (left + right) overflow at large nr_regions. */
+ mid = left + (right - left) / 2;
+ if (addr < regions[mid]->ar.start)
+ right = mid - 1;
+ else if (addr >= regions[mid]->ar.end)
+ left = mid + 1;
+ else {
+ r = regions[mid];
+ break;
+ }
+ }
+
+ if (!r)
+ return;
+ /* Reject reports straddling a region boundary. */
+ if (addr + report->size > r->ar.end)
+ return;
+ if (!r->access_reported) {
+ damon_update_region_access_rate(r, true, &ctx->attrs);
r->access_reported = true;
}
}
@@ -3850,10 +3877,79 @@ static unsigned int kdamond_apply_zero_access_report(struct damon_ctx *ctx)
return max_nr_accesses;
}

+/*
+ * Build a snapshot of the ctx's targets and their region arrays for
+ * use by the ring drain loop.
+ *
+ * The two-pass walk over adaptive_targets is safe even though
+ * krealloc_array() may sleep: target list mutation is funneled
+ * through damon_call onto the kdamond itself, so no other thread
+ * can mutate the list while kdamond is running this function.
+ */
+static struct damon_target_lookup *damon_build_target_lookup(
+ struct damon_ctx *ctx, unsigned int *nr_targets_out)
+{
+ struct damon_target *t;
+ struct damon_target_lookup *tbl;
+ unsigned int nr_targets = 0, total_regions = 0, ti = 0, ri = 0;
+
+ damon_for_each_target(t, ctx) {
+ nr_targets++;
+ total_regions += damon_nr_regions(t);
+ }
+
+ /* Realloc lookups array if needed */
+ if (nr_targets > ctx->drain_snapshot.nr_lookups) {
+ tbl = krealloc_array(ctx->drain_snapshot.lookups,
+ nr_targets, sizeof(*tbl), GFP_KERNEL);
+ if (!tbl)
+ return NULL;
+ ctx->drain_snapshot.lookups = tbl;
+ ctx->drain_snapshot.nr_lookups = nr_targets;
+ }
+ tbl = ctx->drain_snapshot.lookups;
+
+ /* Realloc contiguous region_buf if needed */
+ if (total_regions > ctx->drain_snapshot.region_buf_cap) {
+ struct damon_region **buf;
+
+ buf = krealloc_array(ctx->drain_snapshot.region_buf,
+ total_regions, sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ return NULL;
+ ctx->drain_snapshot.region_buf = buf;
+ ctx->drain_snapshot.region_buf_cap = total_regions;
+ }
+
+ /* Fill lookup table, slicing region_buf across targets */
+ ri = 0;
+ damon_for_each_target(t, ctx) {
+ struct damon_region *r;
+
+ tbl[ti].regions = &ctx->drain_snapshot.region_buf[ri];
+ tbl[ti].nr_regions = damon_nr_regions(t);
+ damon_for_each_region(r, t)
+ ctx->drain_snapshot.region_buf[ri++] = r;
+ ti++;
+ }
+
+ *nr_targets_out = nr_targets;
+ return tbl;
+}
+
static unsigned int kdamond_check_reported_accesses(struct damon_ctx *ctx)
{
int cpu;
- struct damon_target *t;
+ struct damon_target_lookup *tbl;
+ unsigned int nr_targets = 0;
+ unsigned int i;
+
+ tbl = damon_build_target_lookup(ctx, &nr_targets);
+ if (!tbl) {
+ pr_warn_ratelimited(
+ "damon: target-lookup alloc failed; ring drain skipped this tick\n");
+ return kdamond_apply_zero_access_report(ctx);
+ }

for_each_cpu(cpu, &damon_rings_pending) {
struct damon_report_ring *ring =
@@ -3881,14 +3977,19 @@ static unsigned int kdamond_check_reported_accesses(struct damon_ctx *ctx)
while (tail != head) {
struct damon_access_report *report =
&ring->entries[tail];
-
- if (!time_before(report->report_jiffies,
+ if (time_before(report->report_jiffies,
jiffies - usecs_to_jiffies(
- ctx->attrs.sample_interval))) {
- damon_for_each_target(t, ctx)
- kdamond_apply_access_report(
- report, t, ctx);
+ ctx->attrs.sample_interval)))
+ goto next;
+ if (damon_sample_filter_out(report,
+ &ctx->sample_control))
+ goto next;
+ for (i = 0; i < nr_targets; i++) {
+ kdamond_apply_access_report(report,
+ tbl[i].regions,
+ tbl[i].nr_regions, ctx);
}
+next:
tail = (tail + 1) & DAMON_REPORT_RING_MASK;
}
WRITE_ONCE(ring->tail, tail);
--
2.43.0