From: Joanne Koong <joannelkoong@gmail.com>
To: Andrew Morton <akpm@linux-foundation.org>
Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org,
David Hildenbrand <david@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R . Howlett" <liam@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>,
Johannes Weiner <hannes@cmpxchg.org>,
Qi Zheng <qi.zheng@linux.dev>,
Shakeel Butt <shakeel.butt@linux.dev>,
Roman Gushchin <roman.gushchin@linux.dev>,
Muchun Song <muchun.song@linux.dev>, Zi Yan <ziy@nvidia.com>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Ryan Roberts <ryan.roberts@arm.com>,
Barry Song <baohua@kernel.org>, Usama Arif <usama.arif@linux.dev>,
Lance Yang <lance.yang@linux.dev>,
Hugh Dickins <hughd@google.com>
Subject: [PATCH v2] mm: unqueue deferred split folios outside the lruvec lock
Date: Fri, 16 Oct 2026 17:45:55 -0700 [thread overview]
Message-ID: <20261017004555.2890038-1-joannelkoong@gmail.com> (raw)
move_folios_to_lru(), folio_batch_move_lru() and folios_put_refs() call
folio_unqueue_deferred_split() on folios where the refcount has dropped
to zero. In these paths, folio_unqueue_deferred_split() is called with
the lruvec lock often held (move_folios_to_lru() always holds it and
folio_batch_move_lru()/folios_put_refs() usually hold it for every folio
in the batch except the first). However, it's not needed to hold the
lruvec lock.
The lruvec lock protects folio->lru and the reclaim LRU lists whereas
the deferred split queue lives on folio->_deferred_list in
deferred_split_lru. The deferred_split_lru is a list_lru with its own
per-memcg per-node lock.
There are already other existing callers that call
folio_unqueue_deferred_split() without holding the lruvec lock (eg
__folio_put(), shrink_folio_list(), __folio_migrate_mapping(), ...).
Add folio_batch_unqueue_deferred_split() and use it in
move_folios_to_lru(), folio_batch_move_lru(), and folios_put_refs() to
unqueue each batch of zero-refcount folios from the deferred split list
after the lruvec lock is released and before
mem_cgroup_uncharge_folios(). This helps shorten the lruvec lock's
critical sections, which run with IRQs disabled and are highly
contended.
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
---
This patch came out of a discussion about splitting underused mTHP folios [1],
where queueing small mTHP folios on the deferred split list was found to
regress Barry's fault microbenchmark. The cost turned out to be the unqueue
running under the lruvec lock rather than contention on the deferred split
queue itself. Testing this patch on top of the series in [1] with the same
microbenchmark, queueing every 16k folio went from a 15% runtime cost to
noise, with lruvec lock wait dropping from 491 seconds to 38 seconds (most of
that contention then moves to the deferred split list_lru lock, but runtime
still drops back to the unqueued baseline).
[1] https://lore.kernel.org/linux-mm/CAJnrk1a2PpUGTsCgS7jct2KET4K2zAL+_h+te8G1MaU9bXs99w@mail.gmail.com/
v1 -> v2:
- Reword comment in folio_batch_move_lru() (David)
v1 link: https://lore.kernel.org/linux-mm/20261013221307.850803-1-joannelkoong@gmail.com/
mm/folio.c | 24 +++++++++++++++++++-----
mm/internal.h | 1 +
mm/vmscan.c | 3 ++-
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/mm/folio.c b/mm/folio.c
index 35e242b48870..45c814f3857b 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -109,6 +109,19 @@ EXPORT_SYMBOL(__folio_put);
typedef void (*move_fn_t)(struct lruvec *lruvec, struct folio *folio);
+/*
+ * Unqueue a batch of folios from the deferred split queue. This must be
+ * called after the folios' refcounts have dropped to zero and before they
+ * are uncharged (see comment above __folio_unqueue_deferred_split() for why).
+ */
+void folio_batch_unqueue_deferred_split(struct folio_batch *fbatch)
+{
+ unsigned int i;
+
+ for (i = 0; i < folio_batch_count(fbatch); i++)
+ folio_unqueue_deferred_split(fbatch->folios[i]);
+}
+
static void lru_add(struct lruvec *lruvec, struct folio *folio)
{
int was_unevictable = folio_test_clear_unevictable(folio);
@@ -177,14 +190,10 @@ static void folio_batch_move_lru(struct folio_batch *fbatch, move_fn_t move_fn)
*
* We're bypassing normal cleanup. Clear flags that are not
* applicable to dead folios.
- *
- * Since the folio may be part of a huge page, unqueue from
- * deferred split list to avoid a dangling list entry.
*/
if (is_lru_add && folio_ref_freeze(folio, 1)) {
__folio_clear_active(folio);
__folio_clear_unevictable(folio);
- folio_unqueue_deferred_split(folio);
fbatch->folios[i] = NULL;
folio_batch_add(&free_fbatch, folio);
continue;
@@ -201,6 +210,11 @@ static void folio_batch_move_lru(struct folio_batch *fbatch, move_fn_t move_fn)
/* Cleanup filtered dead folios. */
if (is_lru_add) {
+ /*
+ * Unqueue any large folios from the deferred split queue to
+ * avoid a dangling list entry once the folios are freed.
+ */
+ folio_batch_unqueue_deferred_split(&free_fbatch);
mem_cgroup_uncharge_folios(&free_fbatch);
free_unref_folios(&free_fbatch);
}
@@ -1020,7 +1034,6 @@ void folios_put_refs(struct folio_batch *folios, unsigned int *refs)
free_huge_folio(folio);
continue;
}
- folio_unqueue_deferred_split(folio);
__page_cache_release(folio, &lruvec, &flags);
if (j != i)
@@ -1035,6 +1048,7 @@ void folios_put_refs(struct folio_batch *folios, unsigned int *refs)
}
folios->nr = j;
+ folio_batch_unqueue_deferred_split(folios);
mem_cgroup_uncharge_folios(folios);
free_unref_folios(folios);
}
diff --git a/mm/internal.h b/mm/internal.h
index d497c35ee783..33ae388feb33 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -761,6 +761,7 @@ static inline bool folio_unqueue_deferred_split(struct folio *folio)
return __folio_unqueue_deferred_split(folio);
}
+void folio_batch_unqueue_deferred_split(struct folio_batch *fbatch);
static inline struct folio *page_rmappable_folio(struct page *page)
{
diff --git a/mm/vmscan.c b/mm/vmscan.c
index daa7e37c2236..9c71a50309e8 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -2007,9 +2007,9 @@ static unsigned int move_folios_to_lru(struct list_head *list, bool do_rotate)
if (unlikely(folio_put_testzero(folio))) {
__folio_clear_lru_flags(folio);
- folio_unqueue_deferred_split(folio);
if (folio_batch_add(&free_folios, folio) == 0) {
lruvec_unlock_irq(lruvec);
+ folio_batch_unqueue_deferred_split(&free_folios);
mem_cgroup_uncharge_folios(&free_folios);
free_unref_folios(&free_folios);
lruvec = NULL;
@@ -2041,6 +2041,7 @@ static unsigned int move_folios_to_lru(struct list_head *list, bool do_rotate)
lruvec_unlock_irq(lruvec);
if (free_folios.nr) {
+ folio_batch_unqueue_deferred_split(&free_folios);
mem_cgroup_uncharge_folios(&free_folios);
free_unref_folios(&free_folios);
}
--
2.52.0
reply other threads:[~2026-10-17 0:48 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=20261017004555.2890038-1-joannelkoong@gmail.com \
--to=joannelkoong@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=hughd@google.com \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=muchun.song@linux.dev \
--cc=qi.zheng@linux.dev \
--cc=roman.gushchin@linux.dev \
--cc=rppt@kernel.org \
--cc=ryan.roberts@arm.com \
--cc=shakeel.butt@linux.dev \
--cc=surenb@google.com \
--cc=usama.arif@linux.dev \
--cc=vbabka@kernel.org \
--cc=ziy@nvidia.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
all inboxes | Powered by JetHome®