From: Max Kellermann <max.kellermann@ionos.com>
To: akpm@linux-foundation.org, kasong@tencent.com,
qi.zheng@linux.dev, shakeel.butt@linux.dev, baohua@kernel.org,
axelrasmussen@google.com, yuanchu@google.com, weixugc@google.com,
baoquan.he@linux.dev, baolin.wang@linux.alibaba.com,
david@kernel.org, ljs@kernel.org, liam@infradead.org,
vbabka@kernel.org, rppt@kernel.org, surenb@google.com,
mhocko@suse.com, hannes@cmpxchg.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, rcu@vger.kernel.org
Cc: Max Kellermann <max.kellermann@ionos.com>
Subject: [PATCH 0/5] mm: multi-gen LRU: reduce memcg rotation overhead
Date: Mon, 12 Oct 2026 22:54:42 +0200 [thread overview]
Message-ID: <20261012205447.2717801-1-max.kellermann@ionos.com> (raw)
This series addresses excessive time spent rotating memcgs during
MGLRU reclaim on systems with a large number of memcgs.
On some servers with tens of thousands of cgroups, we had repeated
soft lockups and RCU stalls:
rcu: INFO: rcu_sched self-detected stall on CPU
rcu: 60-...!: (1118 ticks this GP) idle=ca74/1/0x4000000000000000 softirq=140067260/140067260 fqs=10
rcu: (t=2101 jiffies g=180256409 q=94266 ncpus=96)
rcu: rcu_sched kthread starved for 2073 jiffies! g180256409 f0x2 RCU_GP_WAIT_FQS(5) ->state=0x0 ->cpu=53
rcu: Unless rcu_sched kthread gets sufficient CPU time, OOM is now expected behavior.
rcu: RCU grace-period kthread stack dump:
task:rcu_sched state:R running task stack:0 pid:15 tgid:15 ppid:2 task_flags:0x208040 flags:0x00000010
Call trace:
__switch_to+0xc4/0x108 (T)
__schedule+0x2b4/0x978
schedule+0x2c/0xc0
schedule_timeout+0x7c/0x110
rcu_gp_fqs_loop+0xec/0x4e8
rcu_gp_kthread+0x12c/0x160
kthread+0x118/0x1e0
ret_from_fork+0x10/0x20
rcu: Stack dump where RCU GP kthread last ran:
Sending NMI from CPU 60 to CPUs 53:
NMI backtrace for cpu 53
CPU: 53 UID: 3758108211 PID: 4052395 Comm: php-cgi8.3 Tainted: G W L 6.18.52-i1-ampere #1203 NONE
Tainted: [W]=WARN, [L]=SOFTLOCKUP
Hardware name: Supermicro ARS-110M-NR/R12SPD-A, BIOS 1.1b 10/17/2023
pstate: 40400009 (nZcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
pc : handle_softirqs+0xa4/0x2e8
lr : __do_softirq+0x18/0x20
[...]
Call trace:
handle_softirqs+0xa4/0x2e8 (P)
__do_softirq+0x18/0x20
____do_softirq+0x14/0x28
call_on_irq_stack+0x30/0x48
do_softirq_own_stack+0x20/0x38
__irq_exit_rcu+0xc0/0x100
irq_exit_rcu+0x14/0x28
el1_interrupt+0x3c/0x58
el1h_64_irq_handler+0x14/0x20
el1h_64_irq+0x6c/0x70
lru_gen_rotate_memcg+0x210/0x280 (P)
shrink_node+0xb14/0xcc8
do_try_to_free_pages+0xd0/0x508
try_to_free_pages+0x11c/0x230
__alloc_frozen_pages_noprof+0x4f8/0xe00
get_free_pages_noprof+0x20/0x60
ceph_alloc_readdir_reply_buffer+0xa4/0x160
ceph_readdir+0x248/0xeb0
wrap_directory_iterator+0x54/0x88
shared_ceph_readdir+0x1c/0x28
The problem was lru_gen_rotate_memcg(). Its memcg tail insertion
walked the destination bin while holding the per-node `memcg_lru.lock`
with interrupts disabled. With so many cgroups, the lock duration
grew too long and thousands of concurrent reclaimers were contending
for the spinlock all the time.
Reclaim can also repeatedly promote memcgs that are already in the
young generation, and scans of empty or protected memcgs can bypass
the rescheduling points in page reclaim.
This patch set contains three parts:
The first 3 patches add a cache for each bin's tail to make tail
insertion constant time.
The fourth patch skips redundant YOUNG rotations when the memcg is
already in the default segment of the current young generation.
The last patch adds rescheduling points between memcg reclaim attempts
and at bin/retry boundaries to avoid potential remaining soft lockups
under extreme pressure.
I ran a micro benchmark on branch 6.18 on a 12-core Xeon 5118: 8
concurrent workers repeatedly write `1024M swappiness=0` to the root
cgroup's `memory.reclaim` for 60 seconds, against a previously
prepared set of 1500 memory cgroups. Each worker issues its next
request when the previous one returns. The test measures request
throughput and latency under concurrent MGLRU reclaim. All requests
returned EAGAIN, indicating that the requested reclaim target was not
met; the reported throughput counts completed reclaim attempts, not
reclaimed bytes.
Results:
Requests/sec Mean latency Maximum latency
Unpatched 4.56 1739.85 ms 4083.55 ms
Patches 1-3 43.42 183.53 ms 393.15 ms
Patches 1-4 59.22 134.03 ms 393.44 ms
Patches 1-5 57.80 137.63 ms 412.72 ms
Constant-time insertion increases request throughput by 9.5x.
Avoiding redundant YOUNG rotations adds another 36.4%. The full patch
series improves the unpatched throughput by a factor of 12.7.
All requests returned EAGAIN. These results measure reclaim attempts
that did not meet their requested target.
I did not measure scheduling-latency improvements of the last patch.
Max Kellermann (5):
include/linux/rculist_nulls: add hlist_nulls_add_behind_rcu()
mm/vmscan: move code to lru_gen_{add,del}_memcg()
mm/mglru: cache the last memcg node, make tail insertion constant time
mm/mglru: avoid redundant rotations of young memcgs
mm/mglru: reschedule between memcg reclaim attempts
include/linux/mmzone.h | 4 ++
include/linux/rculist_nulls.h | 36 ++++++++++++++---
mm/vmscan.c | 76 ++++++++++++++++++++++++++++++-----
3 files changed, 101 insertions(+), 15 deletions(-)
--
2.47.3
next reply other threads:[~2026-10-12 20:55 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 20:54 Max Kellermann [this message]
2026-10-12 20:54 ` [PATCH 1/5] include/linux/rculist_nulls: add hlist_nulls_add_behind_rcu() Max Kellermann
2026-10-12 20:54 ` [PATCH 2/5] mm/vmscan: move code to lru_gen_{add,del}_memcg() Max Kellermann
2026-10-12 20:54 ` [PATCH 3/5] mm/mglru: cache the last memcg node, make tail insertion constant time Max Kellermann
2026-10-12 20:54 ` [PATCH 4/5] mm/mglru: avoid redundant rotations of young memcgs Max Kellermann
2026-10-12 20:54 ` [PATCH 5/5] mm/mglru: reschedule between memcg reclaim attempts Max Kellermann
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261012205447.2717801-1-max.kellermann@ionos.com \
--to=max.kellermann@ionos.com \
--cc=akpm@linux-foundation.org \
--cc=axelrasmussen@google.com \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=baoquan.he@linux.dev \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=kasong@tencent.com \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=qi.zheng@linux.dev \
--cc=rcu@vger.kernel.org \
--cc=rppt@kernel.org \
--cc=shakeel.butt@linux.dev \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=weixugc@google.com \
--cc=yuanchu@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox