[PATCH 26/25] mm/fbatch: drop reference inside the loop when draining
From: Hugh Dickins
Date: Tue Sep 01 2026 - 23:55:02 EST
folio_batch_move_lru() and mlock_folio_batch() used folios_put_refs()
after their loop: but that's counter-productive, to batch up dropping
all the references acquired within the loop. Now folio_put_testzero()
inside the loop, where we also already hold (bar races) the right lock
to remove the folio from its lruvec.
This should be much friendlier to compaction, in the common case when
the folio is still in use, to bring it back to expected refs sooner; but
I suspect that when the folio is freed, compaction won't accept it until
it gets to be PageBuddy later on?
Respect the comment in mm/vmscan.c move_folios_to_lru(): could be done
differently, but it's not worth optimizing the case when we cleared lru,
since it also has to handle the case when we failed to clear it.
Signed-off-by: Hugh Dickins <hughd@xxxxxxxxxx>
---
I shall have to rebase the series, but this is an important
afterthought which is best added into the review now.
mm/folio.c | 23 +++++++++++++++++------
mm/mlock.c | 27 +++++++++++++++++++++------
2 files changed, 38 insertions(+), 12 deletions(-)
diff --git a/mm/folio.c b/mm/folio.c
index 55ca799a9dcd..7c545e4c090d 100644
--- a/mm/folio.c
+++ b/mm/folio.c
@@ -128,20 +128,18 @@ static void lru_add(struct lruvec *lruvec, struct folio *folio)
static void folio_batch_move_lru(struct folio_batch *fbatch, move_fn_t move_fn)
{
- int i;
+ int i, j = 0;
struct lruvec *lruvec = NULL;
unsigned long flags = 0;
for (i = 0; i < folio_batch_count(fbatch); i++) {
struct folio *folio = fbatch->folios[i];
- if (!folio_try_get(folio)) {
- fbatch->folios[i] = NULL;
+ if (!folio_try_get(folio))
continue;
- }
if (!folio_test_clear_lru(folio))
- continue;
+ goto restored_lru;
/* Do not add to LRU if it has already been added */
if (move_fn == lru_add && !lru_add_del_folio(folio))
@@ -155,11 +153,24 @@ static void folio_batch_move_lru(struct folio_batch *fbatch, move_fn_t move_fn)
lruvec_add_folio(lruvec, folio);
restore_lru:
folio_set_lru(folio);
+ /* See mm/vmscan.c move_folios_to_lru() comment on ordering */
+restored_lru:
+ if (unlikely(folio_put_testzero(folio))) {
+ folio_unqueue_deferred_split(folio);
+ __page_cache_release(folio, &lruvec, &flags);
+ fbatch->folios[j++] = folio;
+ }
}
if (lruvec)
lruvec_unlock_irqrestore(lruvec, flags);
- folios_put_refs(fbatch, NULL);
+ if (unlikely(j)) {
+ fbatch->nr = j;
+ mem_cgroup_uncharge_folios(fbatch);
+ free_unref_folios(fbatch);
+ } else {
+ folio_batch_reinit(fbatch);
+ }
}
static void __folio_batch_add_and_move(struct folio_batch __percpu *fbatch,
diff --git a/mm/mlock.c b/mm/mlock.c
index a3cfdb274fc7..c528cf7135bf 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -27,6 +27,7 @@
#include <linux/secretmem.h>
#include "internal.h"
+#include "page_alloc.h"
struct mlock_fbatch {
local_lock_t lock;
@@ -170,28 +171,42 @@ static void mlock_folio_batch(struct folio_batch *fbatch)
struct lruvec *lruvec = NULL;
unsigned long mlock;
struct folio *folio;
- int i;
+ int i, j = 0;
for (i = 0; i < folio_batch_count(fbatch); i++) {
folio = fbatch->folios[i];
mlock = (unsigned long)folio & MLOCK_FLAG;
folio = (struct folio *)((unsigned long)folio - mlock);
- fbatch->folios[i] = folio;
- if (!folio_try_get(folio)) {
- fbatch->folios[i] = NULL;
+ if (!folio_try_get(folio))
continue;
- }
if (mlock)
lruvec = __mlock_folio(folio, lruvec);
else
lruvec = __munlock_folio(folio, lruvec);
+
+ if (unlikely(folio_put_testzero(folio))) {
+ folio_unqueue_deferred_split(folio);
+ /* __page_cache_release() without irqflags */
+ if (folio_test_lru(folio)) {
+ lruvec = folio_lruvec_relock_irq(folio, lruvec);
+ lruvec_del_folio(lruvec, folio);
+ __folio_clear_lru_flags(folio);
+ }
+ fbatch->folios[j++] = folio;
+ }
}
if (lruvec)
lruvec_unlock_irq(lruvec);
- folios_put_refs(fbatch, NULL);
+ if (unlikely(j)) {
+ fbatch->nr = j;
+ mem_cgroup_uncharge_folios(fbatch);
+ free_unref_folios(fbatch);
+ } else {
+ folio_batch_reinit(fbatch);
+ }
}
void mlock_drain_local(void)
--
2.51.0