* [PATCH v3 0/2] mm/damon/core: detect internal variation above max_nr_regions/2
@ 2026-06-29 14:56 SJ Park
2026-06-29 14:56 ` [PATCH v3 1/2] mm/damon/core: split a fraction of regions when nr_regions exceeds max/2 SJ Park
2026-06-29 14:56 ` [PATCH v3 2/2] mm/damon/tests/core-kunit: test split above max_nr_regions/2 SJ Park
0 siblings, 2 replies; 5+ messages in thread
From: SJ Park @ 2026-06-29 14:56 UTC (permalink / raw)
To: Andrew Morton
Cc: SJ Park, Brendan Higgins, David Gow, Jiayuan Chen, Shu Anzai,
damon, kunit-dev, linux-kernel, linux-kselftest, linux-mm
kdamond_split_regions() bails out early when nr_regions is already
above max_nr_regions / 2. A large region that picks up new internal
variation after that point never gets split, so we lose visibility
into its hot/cold structure.
We hit this with damon-paddr on hugepage workloads and damon-vaddr
on processes that mmap a large anonymous range.
Example with max_nr_regions == 1500. A target ends up with 799
small hot/cold regions plus one big region (an earlier merge
collapsed a uniformly-accessed range into a single piece):
H:hot
C:cold
r1 r2 r3 r800
HHHHHH|CCCCCC|HHHHHH|...|HHHHHH..........................|
nr_regions = 800 > max_nr_regions / 2 = 750
Now a cold subarea shows up inside r800:
r1 r2 r3 r800
HHHHHH|CCCCCC|HHHHHH|...|HHHHHH........CCCCCC.............|
The small regions can't merge with each other (their access counts
differ), so budget never frees up. r800 can't be split because
nr_regions > max_nr_regions / 2 returns early. The cold subarea
stays invisible.
Patch 1 keeps refining on this path: when nr_regions is above
max_nr_regions / 2 but still under the maximum, it splits a fraction
of the regions instead of returning. The fraction shrinks as the
remaining budget shrinks, so the count approaches max_nr_regions
smoothly. A useless split is undone by the next merge cycle.
Patch 2 adds a KUnit test for the case where nr_regions is already
above max_nr_regions / 2.
Thanks to SJ for the suggestion to drive the split fraction
from the remaining budget rather than an age-based filter.
Changes from v2
- v2: https://lore.kernel.org/20260626085851.70754-1-jiayuan.chen@linux.dev
- Collect R-b: from SJ.
- Rebase to latest mm-new.
Changes from v1
- v1: https://lore.kernel.org/damon/20260521045236.115749-1-jiayuan.chen@linux.dev/
- Some feedback from SJ.
Jiayuan Chen (2):
mm/damon/core: split a fraction of regions when nr_regions exceeds
max/2
mm/damon/tests/core-kunit: test split above max_nr_regions/2
mm/damon/core.c | 49 +++++++++++++++++++++++++---
mm/damon/tests/core-kunit.h | 64 +++++++++++++++++++++++++++++++++++++
2 files changed, 108 insertions(+), 5 deletions(-)
base-commit: 86100fb7e27ebb5da22fc8a2810eebcf8cc897e8
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 1/2] mm/damon/core: split a fraction of regions when nr_regions exceeds max/2
2026-06-29 14:56 [PATCH v3 0/2] mm/damon/core: detect internal variation above max_nr_regions/2 SJ Park
@ 2026-06-29 14:56 ` SJ Park
2026-06-29 15:08 ` sashiko-bot
2026-06-29 14:56 ` [PATCH v3 2/2] mm/damon/tests/core-kunit: test split above max_nr_regions/2 SJ Park
1 sibling, 1 reply; 5+ messages in thread
From: SJ Park @ 2026-06-29 14:56 UTC (permalink / raw)
To: Andrew Morton
Cc: Jiayuan Chen, Jiayuan Chen, SJ Park, Shu Anzai, damon,
linux-kernel, linux-mm
From: Jiayuan Chen <jiayuan.chen@shopee.com>
kdamond_split_regions() returns early when nr_regions is above
max_nr_regions / 2, leaving internal access variation inside a large
region undetected.
Such a layout is common with damon-paddr on hugepage workloads or
damon-vaddr on processes with a large anonymous mmap.
For example, with max_nr_regions == 1500, a target may end up with
799 small alternating-temperature regions plus one large region that
absorbed a uniformly-accessed range during an earlier merge:
H:hot
C:cold
r1 r2 r3 r800
HHHHHH|CCCCCC|HHHHHH|...|HHHHHH..........................|
nr_regions = 800 > max_nr_regions / 2 = 750
If a cold subarea later emerges inside r800:
r1 r2 r3 r800
HHHHHH|CCCCCC|HHHHHH|...|HHHHHH........CCCCCC.............|
The small regions cannot merge with each other (different access
counts), so the budget stays full. r800 cannot be split because
nr_regions > max_nr_regions / 2 causes an early return. The cold
subarea is never discovered.
When nr_regions is above max_nr_regions / 2 but still under the
maximum, split only a fraction of the regions instead of returning.
One region in every 'max_nr_regions / budget' regions is split, where
budget is the remaining room (max_nr_regions - nr_regions), starting
from a rotating offset so different regions get picked over time. The
fraction shrinks as the budget shrinks, so the region count keeps
refining while approaching max_nr_regions smoothly rather than
overshooting it. An unnecessary split is reverted by the next
kdamond_merge_regions().
Link: https://lore.kernel.org/20260626085851.70754-2-jiayuan.chen@linux.dev
Cc: Jiayuan Chen <jiayuan.chen@linux.dev>
Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
Reviewed-by: SJ Park <sj@kernel.org>
Cc: SJ Park <sj@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Shu Anzai <shu17az@gmail.com>
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/core.c | 49 ++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 44 insertions(+), 5 deletions(-)
diff --git a/mm/damon/core.c b/mm/damon/core.c
index ded76719e8a14..972a19fcee3ec 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -3240,6 +3240,37 @@ static void damon_split_regions_of(struct damon_ctx *ctx,
}
}
+/* Split one in every @split_step regions into two, from a rotating offset */
+static void damon_split_some_regions(struct damon_ctx *ctx,
+ unsigned long split_step)
+{
+ static unsigned long rotation;
+ struct damon_target *t;
+ struct damon_region *r, *next;
+ unsigned long offset = rotation++ % split_step;
+ unsigned long idx = 0;
+
+ damon_for_each_target(t, ctx) {
+ damon_for_each_region_safe(r, next, t) {
+ unsigned long sz_region, sz_sub;
+
+ if (idx++ % split_step != offset)
+ continue;
+ sz_region = damon_sz_region(r);
+ if (sz_region < 2 * ctx->min_region_sz)
+ continue;
+
+ sz_sub = ALIGN_DOWN(damon_rand(ctx, 1, 10) *
+ sz_region / 10, ctx->min_region_sz);
+ /* Do not allow blank region */
+ if (sz_sub == 0 || sz_sub >= sz_region)
+ continue;
+
+ damon_split_region_at(t, r, sz_sub);
+ }
+ }
+}
+
/*
* Split every target region into randomly-sized small regions
*
@@ -3253,25 +3284,33 @@ static void damon_split_regions_of(struct damon_ctx *ctx,
static void kdamond_split_regions(struct damon_ctx *ctx)
{
struct damon_target *t;
- unsigned int nr_regions = 0;
- static unsigned int last_nr_regions;
+ unsigned long nr_regions = 0;
+ unsigned long max_nr_regions = ctx->attrs.max_nr_regions;
+ static unsigned long last_nr_regions;
int nr_subregions = 2;
damon_for_each_target(t, ctx)
nr_regions += damon_nr_regions(t);
- if (nr_regions > ctx->attrs.max_nr_regions / 2)
- return;
+ if (nr_regions >= max_nr_regions)
+ goto done;
+
+ if (nr_regions > max_nr_regions / 2) {
+ damon_split_some_regions(ctx,
+ max_nr_regions / (max_nr_regions - nr_regions));
+ goto done;
+ }
/* Maybe the middle of the region has different access frequency */
if (last_nr_regions == nr_regions &&
- nr_regions < ctx->attrs.max_nr_regions / 3)
+ nr_regions < max_nr_regions / 3)
nr_subregions = 3;
damon_for_each_target(t, ctx)
damon_split_regions_of(ctx, t, nr_subregions,
ctx->min_region_sz);
+done:
last_nr_regions = nr_regions;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 2/2] mm/damon/tests/core-kunit: test split above max_nr_regions/2
2026-06-29 14:56 [PATCH v3 0/2] mm/damon/core: detect internal variation above max_nr_regions/2 SJ Park
2026-06-29 14:56 ` [PATCH v3 1/2] mm/damon/core: split a fraction of regions when nr_regions exceeds max/2 SJ Park
@ 2026-06-29 14:56 ` SJ Park
1 sibling, 0 replies; 5+ messages in thread
From: SJ Park @ 2026-06-29 14:56 UTC (permalink / raw)
To: Andrew Morton
Cc: Jiayuan Chen, Brendan Higgins, David Gow, Jiayuan Chen, SJ Park,
Shu Anzai, damon, kunit-dev, linux-kernel, linux-kselftest,
linux-mm
From: Jiayuan Chen <jiayuan.chen@shopee.com>
Add a test that exercises kdamond_split_regions() when the total
region count is already above max_nr_regions / 2, asserting that the
function still splits a fraction of the regions (makes progress) and
does not overshoot max_nr_regions.
The region size and min_region_sz are picked so the split arithmetic
does not depend on the page size.
All tests pass:
damon: pass:31 fail:0 skip:0 total:31
Totals: pass:31 fail:0 skip:0 total:31
Link: https://lore.kernel.org/20260626085851.70754-3-jiayuan.chen@linux.dev
Cc: Jiayuan Chen <jiayuan.chen@linux.dev>
Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
Reviewed-by: SJ Park <sj@kernel.org>
Cc: SJ Park <sj@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Shu Anzai <shu17az@gmail.com>
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/tests/core-kunit.h | 64 +++++++++++++++++++++++++++++++++++++
1 file changed, 64 insertions(+)
diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h
index c5f5124c3d1f4..a00168730445d 100644
--- a/mm/damon/tests/core-kunit.h
+++ b/mm/damon/tests/core-kunit.h
@@ -335,6 +335,69 @@ static void damon_test_split_regions_of(struct kunit *test)
damon_destroy_ctx(c);
}
+/*
+ * When the total region count is already above max_nr_regions / 2,
+ * kdamond_split_regions() must keep refining the resolution by splitting a
+ * fraction of the regions (making progress), without exceeding
+ * max_nr_regions.
+ */
+static void damon_test_split_above_half_progresses(struct kunit *test)
+{
+ struct damon_ctx *c;
+ struct damon_target *t;
+ struct damon_region *r;
+ unsigned long start;
+ unsigned int nr_before, nr_after, i;
+ const unsigned int nr_init = 760;
+ const unsigned long region_sz = 100;
+
+ c = damon_new_ctx();
+ if (!c)
+ kunit_skip(test, "ctx alloc fail");
+
+ /* Keep the split arithmetic independent of the page size */
+ c->min_region_sz = 1;
+ c->attrs.min_nr_regions = 10;
+ c->attrs.max_nr_regions = 1500;
+
+ t = damon_new_target();
+ if (!t) {
+ damon_destroy_ctx(c);
+ kunit_skip(test, "target alloc fail");
+ }
+
+ for (i = 0; i < nr_init; i++) {
+ start = i * region_sz;
+ r = damon_new_region(start, start + region_sz);
+ if (!r) {
+ damon_free_target(t);
+ damon_destroy_ctx(c);
+ kunit_skip(test, "region alloc fail");
+ }
+ r->nr_accesses = (i & 1) ? 0 : 100;
+ r->age = 5;
+ damon_add_region(r, t);
+ }
+
+ damon_add_target(c, t);
+
+ nr_before = damon_nr_regions(t);
+ /* Above max_nr_regions / 2, so the blanket-split path is skipped */
+ KUNIT_EXPECT_GT(test, (unsigned long)nr_before,
+ c->attrs.max_nr_regions / 2);
+
+ kdamond_split_regions(c);
+
+ nr_after = damon_nr_regions(t);
+ /* Still made progress ... */
+ KUNIT_EXPECT_GT(test, nr_after, nr_before);
+ /* ... but did not overshoot the configured maximum */
+ KUNIT_EXPECT_LE(test, (unsigned long)nr_after,
+ c->attrs.max_nr_regions);
+
+ damon_destroy_ctx(c);
+}
+
static void damon_test_ops_registration(struct kunit *test)
{
struct damon_ctx *c = damon_new_ctx();
@@ -1491,6 +1554,7 @@ static struct kunit_case damon_test_cases[] = {
KUNIT_CASE(damon_test_merge_two),
KUNIT_CASE(damon_test_merge_regions_of),
KUNIT_CASE(damon_test_split_regions_of),
+ KUNIT_CASE(damon_test_split_above_half_progresses),
KUNIT_CASE(damon_test_ops_registration),
KUNIT_CASE(damon_test_set_regions),
KUNIT_CASE(damon_test_nr_accesses_to_accesses_bp),
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 1/2] mm/damon/core: split a fraction of regions when nr_regions exceeds max/2
2026-06-29 14:56 ` [PATCH v3 1/2] mm/damon/core: split a fraction of regions when nr_regions exceeds max/2 SJ Park
@ 2026-06-29 15:08 ` sashiko-bot
2026-06-30 0:19 ` SJ Park
0 siblings, 1 reply; 5+ messages in thread
From: sashiko-bot @ 2026-06-29 15:08 UTC (permalink / raw)
To: SJ Park; +Cc: damon
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Cross-context state corruption and data races due to the use of a static local variable `rotation`.
--
commit 696204689b6ff0a9b6ca3153698c49bfe10cff24
Author: Jiayuan Chen <jiayuan.chen@shopee.com>
mm/damon/core: split a fraction of regions when nr_regions exceeds max/2
This commit modifies the kdamond_split_regions logic to split only a fraction
of regions when the region count exceeds half the maximum, rather than
returning early. It uses a rotating offset to select which regions to split,
refining the count smoothly as it approaches the maximum.
Link: https://lore.kernel.org/20260626085851.70754-2-jiayuan.chen@linux.dev
> diff --git a/mm/damon/core.c b/mm/damon/core.c
> index 027250e43c66f..9bbc60a2a8c05 100644
> --- a/mm/damon/core.c
> +++ b/mm/damon/core.c
[ ... ]
> @@ -3234,6 +3234,37 @@ static void damon_split_regions_of(struct damon_ctx *ctx,
> }
> }
>
> +/* Split one in every @split_step regions into two, from a rotating offset */
> +static void damon_split_some_regions(struct damon_ctx *ctx,
> + unsigned long split_step)
> +{
> + static unsigned long rotation;
> + struct damon_target *t;
> + struct damon_region *r, *next;
> + unsigned long offset = rotation++ % split_step;
[Severity: Medium]
Will this shared static variable cause data races and logic flaws if
multiple DAMON contexts are active?
Since rotation is declared statically, multiple kdamond threads could
concurrently increment it without synchronization. This might lead to
data races and cause different contexts to overwrite each other's
rotation offsets, breaking the isolation of the splitting algorithm and
resulting in uneven or skipped splits in some contexts.
Should this state be kept per-context instead of using a static local
variable?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260629145630.134891-1-sj@kernel.org?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 1/2] mm/damon/core: split a fraction of regions when nr_regions exceeds max/2
2026-06-29 15:08 ` sashiko-bot
@ 2026-06-30 0:19 ` SJ Park
0 siblings, 0 replies; 5+ messages in thread
From: SJ Park @ 2026-06-30 0:19 UTC (permalink / raw)
To: sashiko-bot; +Cc: SJ Park, damon
On Mon, 29 Jun 2026 15:08:10 +0000 sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] Cross-context state corruption and data races due to the use of a static local variable `rotation`.
> --
>
> commit 696204689b6ff0a9b6ca3153698c49bfe10cff24
> Author: Jiayuan Chen <jiayuan.chen@shopee.com>
>
> mm/damon/core: split a fraction of regions when nr_regions exceeds max/2
>
> This commit modifies the kdamond_split_regions logic to split only a fraction
> of regions when the region count exceeds half the maximum, rather than
> returning early. It uses a rotating offset to select which regions to split,
> refining the count smoothly as it approaches the maximum.
>
> Link: https://lore.kernel.org/20260626085851.70754-2-jiayuan.chen@linux.dev
>
> > diff --git a/mm/damon/core.c b/mm/damon/core.c
> > index 027250e43c66f..9bbc60a2a8c05 100644
> > --- a/mm/damon/core.c
> > +++ b/mm/damon/core.c
> [ ... ]
> > @@ -3234,6 +3234,37 @@ static void damon_split_regions_of(struct damon_ctx *ctx,
> > }
> > }
> >
> > +/* Split one in every @split_step regions into two, from a rotating offset */
> > +static void damon_split_some_regions(struct damon_ctx *ctx,
> > + unsigned long split_step)
> > +{
> > + static unsigned long rotation;
> > + struct damon_target *t;
> > + struct damon_region *r, *next;
> > + unsigned long offset = rotation++ % split_step;
>
> [Severity: Medium]
> Will this shared static variable cause data races and logic flaws if
> multiple DAMON contexts are active?
>
> Since rotation is declared statically, multiple kdamond threads could
> concurrently increment it without synchronization. This might lead to
> data races and cause different contexts to overwrite each other's
> rotation offsets, breaking the isolation of the splitting algorithm and
> resulting in uneven or skipped splits in some contexts.
>
> Should this state be kept per-context instead of using a static local
> variable?
As I replied [1] to same question on v2, We don't need a strict round robin
here. We need just a level of fairness. I think this is no problem. Of
course we could revisit this if it turns out to a real problem in the real
world.
[1] https://lore.kernel.org/20260626144906.86649-1-sj@kernel.org
Thanks,
SJ
[...]
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-06-30 0:19 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-29 14:56 [PATCH v3 0/2] mm/damon/core: detect internal variation above max_nr_regions/2 SJ Park
2026-06-29 14:56 ` [PATCH v3 1/2] mm/damon/core: split a fraction of regions when nr_regions exceeds max/2 SJ Park
2026-06-29 15:08 ` sashiko-bot
2026-06-30 0:19 ` SJ Park
2026-06-29 14:56 ` [PATCH v3 2/2] mm/damon/tests/core-kunit: test split above max_nr_regions/2 SJ Park
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox