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 3/5] mm/mglru: cache the last memcg node, make tail insertion constant time
Date: Mon, 12 Oct 2026 22:54:45 +0200 [thread overview]
Message-ID: <20261012205447.2717801-4-max.kellermann@ionos.com> (raw)
In-Reply-To: <20261012205447.2717801-1-max.kellermann@ionos.com>
Rotating or onlining a memcg currently uses hlist_nulls_add_tail_rcu()
which walks the destination bin to find its tail while holding
`memcg_lru.lock` with interrupts disabled. With thousands of cgroups,
this takes a long time and causes severe lock contention.
Cache a pointer to each bin's last node, making tail insertion O(1).
This requires remembering the bin number (but the new struct field
occupies a hole, so it does not grow).
In a micro benchmark that creates 1500 cgroups and repeatedly writes
`1024M swappiness=0` to `memory.reclaim` in 8 threads, the request
rate increases by a factor of 9.5 and the average duration of each
write request is reduced by 89%.
All writes return EAGAIN in both runs; the benchmark measures the cost
of reclaim attempts that cannot meet their requested target, not
reclaimed throughput.
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
---
include/linux/mmzone.h | 4 ++++
mm/vmscan.c | 24 +++++++++++++++++++++---
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 94f9c3ff5416..9c1c1f153d1f 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -586,6 +586,8 @@ struct lru_gen_folio {
u8 gen;
/* the list segment this lru_gen_folio belongs to */
u8 seg;
+ /* the memcg bin, protected by memcg_lru.lock */
+ u8 bin;
/* per-node lru_gen_folio list for global reclaim */
struct hlist_nulls_node list;
};
@@ -683,6 +685,8 @@ struct lru_gen_memcg {
unsigned long nr_memcgs[MEMCG_NR_GENS];
/* per-node lru_gen_folio list for global reclaim */
struct hlist_nulls_head fifo[MEMCG_NR_GENS][MEMCG_NR_BINS];
+ /* last entry in each bin, or NULL if empty */
+ struct hlist_nulls_node *tail[MEMCG_NR_GENS][MEMCG_NR_BINS];
/* protects the above */
spinlock_t lock;
};
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 3d4bc12cf18e..69976df5d537 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4384,21 +4384,37 @@ static void lru_gen_add_memcg(struct lru_gen_memcg *memcg_lru,
int gen, int bin, bool at_head)
{
struct hlist_nulls_head *head = &memcg_lru->fifo[gen][bin];
+ struct hlist_nulls_node *last = memcg_lru->tail[gen][bin];
lockdep_assert_held(&memcg_lru->lock);
- if (at_head)
+ lrugen->bin = bin;
+
+ if (at_head || !last)
hlist_nulls_add_head_rcu(&lrugen->list, head);
else
- hlist_nulls_add_tail_rcu(&lrugen->list, head);
+ hlist_nulls_add_behind_rcu(&lrugen->list, last);
+
+ if (!at_head || !last)
+ memcg_lru->tail[gen][bin] = &lrugen->list;
}
static void lru_gen_del_memcg(struct lru_gen_memcg *memcg_lru,
struct lru_gen_folio *lrugen,
int gen)
{
+ const int bin = lrugen->bin;
+ struct hlist_nulls_head *head = &memcg_lru->fifo[gen][bin];
+
lockdep_assert_held(&memcg_lru->lock);
+ /* pprev points to head->first for the only entry, otherwise prev->next. */
+ if (memcg_lru->tail[gen][bin] == &lrugen->list)
+ memcg_lru->tail[gen][bin] = lrugen->list.pprev == &head->first
+ ? NULL
+ : container_of(lrugen->list.pprev, struct hlist_nulls_node, next);
+
+ /* Keep next intact for readers that still hold this entry. */
hlist_nulls_del_init_rcu(&lrugen->list);
}
@@ -5883,8 +5899,10 @@ void lru_gen_init_pgdat(struct pglist_data *pgdat)
spin_lock_init(&pgdat->memcg_lru.lock);
for (i = 0; i < MEMCG_NR_GENS; i++) {
- for (j = 0; j < MEMCG_NR_BINS; j++)
+ for (j = 0; j < MEMCG_NR_BINS; j++) {
INIT_HLIST_NULLS_HEAD(&pgdat->memcg_lru.fifo[i][j], i);
+ pgdat->memcg_lru.tail[i][j] = NULL;
+ }
}
}
--
2.47.3
next prev parent 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 [PATCH 0/5] mm: multi-gen LRU: reduce memcg rotation overhead Max Kellermann
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 ` Max Kellermann [this message]
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-4-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.