From: "Vlastimil Babka (SUSE)" <vbabka@kernel.org>
To: Hugh Dickins <hughd@google.com>,
Andrew Morton <akpm@linux-foundation.org>
Cc: Ackerley Tng <ackerleytng@google.com>,
Alexander Viro <viro@zeniv.linux.org.uk>,
Alexandre Ghiti <alex@ghiti.fr>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Barry Song <baohua@kernel.org>,
Binbin Wu <binbin.wu@linux.intel.com>,
Christian Brauner <brauner@kernel.org>,
Christoph Hellwig <hch@lst.de>, Christoph Lameter <cl@gentwo.org>,
Claudio Imbrenda <imbrenda@linux.ibm.com>,
David Hildenbrand <david@kernel.org>,
JP Kobryn <jp.kobryn@linux.dev>, Jan Kara <jack@suse.cz>,
Jens Axboe <axboe@kernel.dk>,
Johannes Weiner <hannes@cmpxchg.org>,
Kairui Song <ryncsn@gmail.com>, Kiryl Shutsemau <kas@kernel.org>,
Lance Yang <lance.yang@linux.dev>,
Leonardo Bras <leobras.c@gmail.com>,
Lorenzo Stoakes <ljs@kernel.org>,
Marcelo Tosatti <mtosatti@redhat.com>,
Matthew Wilcox <willy@infradead.org>,
Mel Gorman <mgorman@techsingularity.net>,
Miaohe Lin <linmiaohe@huawei.com>, Michal Hocko <mhocko@suse.com>,
Minchan Kim <minchan@kernel.org>,
Muchun Song <muchun.song@linux.dev>,
Oscar Salvador <osalvador@suse.de>,
Peter Zijlstra <peterz@infradead.org>,
Qi Zheng <qi.zheng@linux.dev>, Rik van Riel <riel@surriel.com>,
Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
Shakeel Butt <shakeel.butt@linux.dev>,
Suren Baghdasaryan <surenb@google.com>,
Yang Shi <yang@os.amperecomputing.com>,
Yu Zhao <yuzhao@google.com>, Zach O'Keefe <zokeefe@google.com>,
Zi Yan <ziy@nvidia.com>,
linux-block@vger.kernel.org, linux-fsdevel@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-mm@kvack.org
Subject: Re: [PATCH v2 04/26] mm/fbatch: lru bit set, no extra ref, while folio on per-cpu fbatch
Date: Wed, 9 Sep 2026 14:25:48 +0200 [thread overview]
Message-ID: <296bf543-23e0-417a-a729-c222392a1777@kernel.org> (raw)
In-Reply-To: <61e15506-940f-3532-5fc9-4086f7612c10@google.com>
On 9/9/26 11:49, Hugh Dickins wrote:
> Treat folios on a per-cpu fbatch as if they were already on the lruvec:
> with PG_lru set, without holding an extra reference. This will enable
> the removal of most lru_add_drain() and lru_add_drain_all() calls soon.
>
> Recognize such a folio by 0x02 set in the folio->lru.next pointer by
> folio_add_lru(). Then lruvec_del_folio() (aided by "lru_add_del_folio")
I admit I find the name lru_add_del_folio() quite confusing. What it does is
AFAIU try to delete from a folio_batch, which is not necessarily the lru_add
one?
I don't have an alternative proposal ready though, naming is hard.
> can pretend to unlink it, and folio_batch_move_lru()'s lru_add case can
> check whether one of the others has already moved it to lruvec.
>
> (That bit is also used in a transient way by set_page_pfmemalloc(), to
> inform interested callers whether page_is_pfmemalloc(): but those callers
> are in networking, not putting folios on LRU; and accept that any use of
> the page->lru field already erases page_is_pfmemalloc() information.)
>
> Let folio->lru.next point to the lru_add fbatch entry, but this is now
> just for debugging: it seemed to be important for folio_batch_move_lru()
> to distinguish fresh from stale entries, but then it turned out that it
> has to processs them identically.
>
> Activate, deactivates and move_tail, holding no reference on the folio,
> might come to act on a stale folio when the fbatch is drained: but it's
> acquired by try_get and test_clear_lru, so safe even when suboptimal.
>
> Reclaim is not an exact science, and there have been no complaints of
> missed actions since 5.11 commit fc574c23558c ("mm/swap.c: serialize
> memcg changes in pagevec_lru_move_fn") introduced the TestClearPageLRU
> protocol: so don't expect complaints of a few surprisingly taken actions.
>
> Signed-off-by: Hugh Dickins <hughd@google.com>
Tricky, and I guess my questions below will betray I didn't grasp all the
nuances...
> ---
> include/linux/mm_inline.h | 25 ++++++++
> include/linux/mm_types.h | 6 +-
> mm/folio.c | 119 +++++++++++++-------------------------
> mm/huge_memory.c | 6 +-
> 4 files changed, 74 insertions(+), 82 deletions(-)
>
> diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h
> index 621c8653d8f7..8420b1276535 100644
> --- a/include/linux/mm_inline.h
> +++ b/include/linux/mm_inline.h
> @@ -343,6 +343,29 @@ static inline void folio_migrate_refs(struct folio *new, const struct folio *old
> }
> #endif /* CONFIG_LRU_GEN */
>
> +enum {
> + LRU_NEXT_NEVER_TAIL = 0, /* Used by a tail's compound_head */
> + LRU_NEXT_BATCHED = 1, /* Not used by any aligned pointer */
> + NR_LRU_NEXT_FLAGS
> +};
> +
> +static __always_inline
> +bool lru_add_del_folio(struct folio *folio)
> +{
> + unsigned long lru_next = READ_ONCE(folio->lru_next);
> +
> + /* BUG_ON(folio_test_lru(folio) && folio_ref_count(folio)); */
Maybe do it as VM_WARN_ON_ONCE() and then it's acceptable for mainline and
gets excercised by bots?
> + if (!(lru_next & BIT(LRU_NEXT_BATCHED)))
> + return false;
> +
> + WRITE_ONCE(folio->lru.next, LIST_POISON1);
> + /* BUG_ON(folio->lru_next & BIT(LRU_NEXT_BATCHED)); */
Is this a test for unexpected LIST_POISON1 definitions (in which case
BUILD_BUG_ON would work?) or a test for a race that should not happen,
however with a very tiny detection window?
> +
> + /* Ensure folio->lru_next visible when folio_set_lru() called later */
> + smp_mb__before_atomic();
> + return true;
> +}
> +
> static __always_inline
> void lruvec_add_folio(struct lruvec *lruvec, struct folio *folio)
> {
> @@ -384,6 +407,8 @@ void lruvec_del_folio(struct lruvec *lruvec, struct folio *folio)
>
> if (lru_gen_del_folio(lruvec, folio, false))
> return;
> + if (lru_add_del_folio(folio))
> + return;
>
> if (lru != LRU_UNEVICTABLE)
> list_del(&folio->lru);
> diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
> index 6d815f6440c9..fe6220b97cf3 100644
> --- a/include/linux/mm_types.h
> +++ b/include/linux/mm_types.h
> @@ -85,6 +85,8 @@ struct page {
> * WARNING: bit 0 of the first word is used for PageTail(). That
> * means the other users of this union MUST NOT use the bit to
> * avoid collision and false-positive PageTail().
> + * Bit 1 of the first word is used by page_is_pfmemalloc().
> + * Bit 1 of the first word (lru_next) is also used by folio_add_lru().
> */
> union {
> struct { /* Page cache and anonymous pages */
> @@ -410,10 +412,8 @@ struct folio {
> union {
> struct list_head lru;
> /* private: avoid cluttering the output */
> - /* For the Unevictable "LRU list" slot */
> struct {
> - /* Avoid compound_info */
> - void *__filler;
> + unsigned long lru_next;
> /* public: */
> unsigned int mlock_count;
> /* private: */
> diff --git a/mm/folio.c b/mm/folio.c
> index b9dc4f5e10a6..e743cd539b9e 100644
> --- a/mm/folio.c
> +++ b/mm/folio.c
> @@ -152,57 +152,34 @@ static void folio_batch_move_lru(struct folio_batch *fbatch, move_fn_t move_fn)
> int i;
> struct lruvec *lruvec = NULL;
> unsigned long flags = 0;
> - struct folio_batch free_fbatch;
> - bool is_lru_add = (move_fn == lru_add);
> -
> - /*
> - * If we're adding to the LRU, preemptively filter dead folios. Use
> - * this dedicated folio batch for temp storage and deferred cleanup.
> - */
> - if (is_lru_add)
> - folio_batch_init(&free_fbatch);
>
> for (i = 0; i < folio_batch_count(fbatch); i++) {
> struct folio *folio = fbatch->folios[i];
>
> - /* block memcg migration while the folio moves between lru */
> - if (!is_lru_add && !folio_test_clear_lru(folio))
> - continue;
> -
> - /*
> - * Filter dead folios by moving them from the add batch to the temp
> - * batch for freeing after this loop.
> - *
> - * 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);
> + if (!folio_try_get(folio)) {
> fbatch->folios[i] = NULL;
> - folio_batch_add(&free_fbatch, folio);
> continue;
> }
>
> + if (!folio_test_clear_lru(folio))
> + continue;
> +
> + /* Do not add to LRU if it has already been added */
> + if (move_fn == lru_add && !lru_add_del_folio(folio))
> + goto restore_lru;
> +
> folio_lruvec_relock_irqsave(folio, &lruvec, &flags);
> move_fn(lruvec, folio);
>
> + /* Do add to LRU if not already there (move_fn skipped) */
> + if (lru_add_del_folio(folio))
> + lruvec_add_folio(lruvec, folio);
AFAICS the previous code didn't do anything like this (and move_fn could
skip or not all the same?) , and I wonder if it's now (sorry) load-bearing,
or an optimization?
> +restore_lru:
> folio_set_lru(folio);
> }
>
> if (lruvec)
> lruvec_unlock_irqrestore(lruvec, flags);
> -
> - /* Cleanup filtered dead folios. */
> - if (is_lru_add) {
> - mem_cgroup_uncharge_folios(&free_fbatch);
> - free_unref_folios(&free_fbatch);
> - }
> -
> folios_put(fbatch);
> }
>
> @@ -211,8 +188,6 @@ static void __folio_batch_add_and_move(struct folio_batch __percpu *fbatch,
> {
> unsigned long flags;
>
> - folio_get(folio);
> -
> if (disable_irq)
> local_lock_irqsave(&cpu_fbatches.lock_irq, flags);
> else
> @@ -273,7 +248,6 @@ static void lru_activate(struct lruvec *lruvec, struct folio *folio)
> if (folio_test_active(folio) || folio_test_unevictable(folio))
> return;
>
> -
> lruvec_del_folio(lruvec, folio);
> folio_set_active(folio);
> lruvec_add_folio(lruvec, folio);
> @@ -289,37 +263,12 @@ void folio_activate(struct folio *folio)
> !folio_test_lru(folio))
> return;
>
> - folio_batch_add_and_move(folio, lru_activate);
> -}
> -
> -static void __lru_cache_activate_folio(struct folio *folio)
> -{
> - struct folio_batch *fbatch;
> - int i;
> -
> - local_lock(&cpu_fbatches.lock);
> - fbatch = this_cpu_ptr(&cpu_fbatches.lru_add);
> -
> /*
> - * Search backwards on the optimistic assumption that the folio being
> - * activated has just been added to this batch. Note that only
> - * the local batch is examined as a !LRU folio could be in the
> - * process of being released, reclaimed, migrated or on a remote
> - * batch that is currently being drained. Furthermore, marking
> - * a remote batch's folio active potentially hits a race where
> - * a folio is marked active just after it is added to the inactive
> - * list causing accounting errors and BUG_ON checks to trigger.
> + * XXX: It is curiously difficult to recreate safely the old
> + * __lru_cache_activate_folio() optimization (folio_set_active()
> + * directly if it's on the local lru_add fbatch): revisit later.
> */
> - for (i = folio_batch_count(fbatch) - 1; i >= 0; i--) {
> - struct folio *batch_folio = fbatch->folios[i];
> -
> - if (batch_folio == folio) {
> - folio_set_active(folio);
> - break;
> - }
> - }
> -
> - local_unlock(&cpu_fbatches.lock);
> + folio_batch_add_and_move(folio, lru_activate);
> }
>
> #ifdef CONFIG_LRU_GEN
> @@ -410,16 +359,7 @@ void folio_mark_accessed(struct folio *folio)
> * unevictable page accessed has no effect.
> */
> } else if (!folio_test_active(folio)) {
> - /*
> - * If the folio is on the LRU, queue it for activation via
> - * cpu_fbatches.lru_activate. Otherwise, assume the folio is in a
> - * folio_batch, mark it active and it'll be moved to the active
> - * LRU on the next drain.
> - */
> - if (folio_test_lru(folio))
> - folio_activate(folio);
> - else
> - __lru_cache_activate_folio(folio);
> + folio_activate(folio);
> folio_clear_referenced(folio);
> workingset_activation(folio);
> }
> @@ -439,6 +379,10 @@ EXPORT_SYMBOL(folio_mark_accessed);
> */
> void folio_add_lru(struct folio *folio)
> {
> + struct folio_batch *fbatch;
> + unsigned long lru_next;
> + bool full;
> +
> VM_BUG_ON_FOLIO(folio_test_active(folio) &&
> folio_test_unevictable(folio), folio);
> VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
> @@ -458,7 +402,26 @@ void folio_add_lru(struct folio *folio)
> folio_mark_accessed(folio);
> }
>
> - folio_batch_add_and_move(folio, lru_add);
> + local_lock(&cpu_fbatches.lock);
> + fbatch = this_cpu_ptr(&cpu_fbatches.lru_add);
> +
> + /* Storing this address is only for debugging */
> + lru_next = (unsigned long)&fbatch->folios[fbatch->nr];
> + /* This mask will do nothing on 64-bit */
> + lru_next &= ~(BIT(NR_LRU_NEXT_FLAGS) - 1);
> + lru_next |= BIT(LRU_NEXT_BATCHED);
> + folio->lru_next = lru_next;
> +
> + full = !folio_batch_add(fbatch, folio);
> +
> + /* Ensure folio->lru_next visible to folio_test_clear_lru() callers */
> + smp_mb__before_atomic();
> + folio_set_lru(folio);
> +
> + if (full || !folio_may_be_lru_cached(folio) || lru_cache_disabled())
> + folio_batch_move_lru(fbatch, lru_add);
> +
> + local_unlock(&cpu_fbatches.lock);
> }
> EXPORT_SYMBOL(folio_add_lru);
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index afbb5974bd22..c7510d875433 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3995,8 +3995,12 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
> }
>
> /* lock lru list/PageCompound, ref frozen by page_ref_freeze */
> - if (do_lru)
> + if (do_lru) {
> lruvec = folio_lruvec_lock(folio);
> + /* Move from fbatch to lruvec before lru_add_split_folio()s */
> + if (lru_add_del_folio(folio))
> + lruvec_add_folio(lruvec, folio);
This is not mentioned in the changelog. Why is it necessary now?
> + }
>
> ret = __split_unmapped_folio(folio, new_order, split_at, xas,
> mapping, split_type);
next prev parent reply other threads:[~2026-09-09 12:25 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-09 9:39 [PATCH v2 00/26] mm/fbatch: drain lru_add_drain() and _all() Hugh Dickins
2026-09-09 9:42 ` [PATCH v2 01/26] mm/fbatch: remove !CONFIG_SMP special case of folio_activate() Hugh Dickins
2026-09-09 9:44 ` [PATCH v2 02/26] mm/fbatch: allow folios_put_refs() to skip xa_is_value() entries Hugh Dickins
2026-09-09 9:46 ` [PATCH v2 03/26] mm/fbatch: temporarily disable lazyfree and mlock+munlock batching Hugh Dickins
2026-09-09 9:49 ` [PATCH v2 04/26] mm/fbatch: lru bit set, no extra ref, while folio on per-cpu fbatch Hugh Dickins
2026-09-09 12:25 ` Vlastimil Babka (SUSE) [this message]
2026-09-12 19:30 ` Hugh Dickins
2026-09-09 9:51 ` [PATCH v2 05/26] mm/fbatch: lru_add_del_folio()+folio_add_lru() after clear_lru() Hugh Dickins
2026-09-09 15:04 ` Vlastimil Babka (SUSE)
2026-09-12 21:59 ` Hugh Dickins
2026-09-09 9:53 ` [PATCH v2 06/26] mm/fbatch: fbatch_drain_lazyfree(onstack fbatch) before ptl unlock Hugh Dickins
2026-09-09 21:02 ` Vlastimil Babka (SUSE)
2026-09-12 22:07 ` Hugh Dickins
2026-09-09 9:55 ` [PATCH v2 07/26] mm/fbatch: LRU_NEXT_ACTIVATE bit to optimize folio_activate() Hugh Dickins
2026-09-10 12:01 ` Vlastimil Babka (SUSE)
2026-09-12 22:35 ` Hugh Dickins
2026-09-14 19:55 ` Hugh Dickins
2026-09-10 16:42 ` Kiryl Shutsemau
2026-09-12 23:33 ` Hugh Dickins
2026-09-14 20:19 ` Hugh Dickins
2026-09-15 13:08 ` Kiryl Shutsemau
2026-09-09 9:57 ` [PATCH v2 08/26] mm/fbatch: replace mlock_new_folio() by __folio_add_lru(,mlockit) Hugh Dickins
2026-09-10 17:47 ` Vlastimil Babka (SUSE)
2026-09-09 9:59 ` [PATCH v2 09/26] mm/fbatch: restore mlock+munlock batching, without extra ref Hugh Dickins
2026-09-10 21:05 ` Vlastimil Babka (SUSE)
2026-09-12 23:46 ` Hugh Dickins
2026-09-14 8:16 ` Vlastimil Babka (SUSE)
2026-09-09 10:01 ` [PATCH v2 10/26] mm/fbatch: remove several uses of mlock_drain_local() Hugh Dickins
2026-09-09 10:03 ` [PATCH v2 11/26] mm/fbatch: remove migration's PAGE_WAS_MLOCKED lru_add_drain() Hugh Dickins
2026-09-09 10:05 ` [PATCH v2 12/26] mm/fbatch: remove percpu_pvec_drained and folios_put() Hugh Dickins
2026-09-09 10:08 ` [PATCH v2 13/26] mm/fbatch: no lru_add_drain to collect_longterm_unpinnable_folios() Hugh Dickins
2026-09-09 10:10 ` [PATCH v2 14/26] mm/fbatch: no lru_add_drain() nor _all() for memfd_wait_for_pins() Hugh Dickins
2026-09-09 10:12 ` [PATCH v2 15/26] mm/fbatch: remove shake_folio() shake_page() from memory-failure Hugh Dickins
2026-09-09 10:14 ` [PATCH v2 16/26] mm/fbatch: remove lru_cache_disable() from NUMA folio migration Hugh Dickins
2026-09-09 10:16 ` [PATCH v2 17/26] mm/fbatch: no lru_cache_disable() in __alloc_contig_migrate_range() Hugh Dickins
2026-09-09 10:18 ` [PATCH v2 18/26] mm/fbatch: remove lru_add_drain() and _all() calls from various Hugh Dickins
2026-09-09 10:20 ` [PATCH v2 19/26] mm/fbatch: vm/stat_refresh include lru_add_drain() on each cpu Hugh Dickins
2026-09-09 10:23 ` [PATCH v2 20/26] s390/fbatch: no lru_add_drain_all() in s390_wiggle_split_folio() Hugh Dickins
2026-09-09 10:25 ` [PATCH v2 21/26] block/fbatch: no lru_add_drain_all() in invalidate_bdev() Hugh Dickins
2026-09-09 10:27 ` [PATCH v2 22/26] fs/fbatch: drop_caches invalidate_bh_lrus() not lru_add_drain_all() Hugh Dickins
2026-09-09 10:30 ` [PATCH v2 23/26] fs,mm/fbatch: use invalidate_bh_lrus() not invalidate_bh_lrus_cpu() Hugh Dickins
2026-09-09 10:33 ` [PATCH v2 24/26] fs,mm/fbatch: lru_cache_disable() keep off buffer_head lrus only Hugh Dickins
2026-09-09 10:35 ` [PATCH v2 25/26] mm/fbatch: move lru_add_drain_all() declaration to mm/internal.h Hugh Dickins
2026-09-09 10:37 ` [PATCH v2 26/26] mm/fbatch: drop reference inside the loop when draining Hugh Dickins
2026-09-09 10:41 ` [PATCH v2 27/26] mm/fbatch: paranoid folio vmstats in folio_batch_move_lru() Hugh Dickins
2026-09-26 2:07 ` [PATCH v2 00/26] mm/fbatch: drain lru_add_drain() and _all() JP Kobryn
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=296bf543-23e0-417a-a729-c222392a1777@kernel.org \
--to=vbabka@kernel.org \
--cc=ackerleytng@google.com \
--cc=akpm@linux-foundation.org \
--cc=alex@ghiti.fr \
--cc=axboe@kernel.dk \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=bigeasy@linutronix.de \
--cc=binbin.wu@linux.intel.com \
--cc=brauner@kernel.org \
--cc=cl@gentwo.org \
--cc=david@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=hch@lst.de \
--cc=hughd@google.com \
--cc=imbrenda@linux.ibm.com \
--cc=jack@suse.cz \
--cc=jp.kobryn@linux.dev \
--cc=kas@kernel.org \
--cc=lance.yang@linux.dev \
--cc=leobras.c@gmail.com \
--cc=linmiaohe@huawei.com \
--cc=linux-block@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mgorman@techsingularity.net \
--cc=mhocko@suse.com \
--cc=minchan@kernel.org \
--cc=mtosatti@redhat.com \
--cc=muchun.song@linux.dev \
--cc=osalvador@suse.de \
--cc=peterz@infradead.org \
--cc=qi.zheng@linux.dev \
--cc=riel@surriel.com \
--cc=ryncsn@gmail.com \
--cc=shakeel.butt@linux.dev \
--cc=surenb@google.com \
--cc=viro@zeniv.linux.org.uk \
--cc=willy@infradead.org \
--cc=yang@os.amperecomputing.com \
--cc=yuzhao@google.com \
--cc=ziy@nvidia.com \
--cc=zokeefe@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.