From: Andrew Morton <akpm@linux-foundation.org>
To: mm-commits@vger.kernel.org,shikemeng@huaweicloud.com,nphamcs@gmail.com,kasong@tencent.com,chrisl@kernel.org,baoquan.he@linux.dev,baohua@kernel.org,youngjun.park@lge.com,akpm@linux-foundation.org
Subject: [merged mm-stable] mm-swap-scan-by-cluster-in-find_next_to_unuse.patch removed from -mm tree
Date: Thu, 08 Oct 2026 22:57:56 -0700 [thread overview]
Message-ID: <20261009055756.A76061F000FF@smtp.kernel.org> (raw)
The quilt patch titled
Subject: mm/swap: scan by cluster in find_next_to_unuse()
has been removed from the -mm tree. Its filename was
mm-swap-scan-by-cluster-in-find_next_to_unuse.patch
This patch was dropped because it was merged into the mm-stable branch
of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
------------------------------------------------------
From: Youngjun Park <youngjun.park@lge.com>
Subject: mm/swap: scan by cluster in find_next_to_unuse()
Date: Thu, 10 Sep 2026 01:15:52 +0900
find_next_to_unuse() walks every offset from 0 to si->max, and swapoff
restarts that walk on each retry, so the cost scales with the size of the
device rather than with the few slots the shmem and mmlist passes could
not free. It has caused stalls before.
The flat walk predates the swap table. Slot state now lives in a per
cluster table, and wait_for_allocation() stops all allocation before
try_to_unuse() runs, so a cluster that holds no slot in use stays that
way. Skip such a cluster instead of reading all of its entries.
Fill a 1 TiB swap up to some amount, then swapoff. What is left sits at
the top of what was filled, so every slot below it is free. Medians over
11 pairs at 32 and 128 GiB, 3 pairs at 256 and 512.
filled swapoff
old new
32 GiB 92.4ms 66.3ms
128 GiB 157.6ms 94.4ms
256 GiB 209.7ms 63.8ms
512 GiB 391.8ms 73.4ms
old grows with how much was filled, new does not.
In the ordinary case swap still holds real data and swapoff spends its
time reading it back. it is tested 4 GiB on an 8 GiB device, where the
scan is 1.4% of try_to_unuse(), and there is no difference either way.
Commit dc644a073769 ("mm: add three more cond_resched() in swapoff")
answered those stalls with a cond_resched() every 256 offsets. A walk
bounded by one cluster no longer needs that counter. The loop now runs at
most SWAPFILE_CLUSTER times before it returns or reschedules, the same
bound swap_reclaim_full_clusters() already scans between cond_resched()
calls.
The scan end is clamped to si->max, so the walk stops there rather than
running into the masked tail of the last cluster.
ci->count is read without ci->lock, so READ_ONCE() marks the read for
KCSAN. Allocation is already stopped, so the count can only drop, and a
slot stops being counted only after its folio has left the swap cache. An
empty cluster therefore holds nothing for try_to_unuse() to act on.
Link: https://lore.kernel.org/20260909161552.2335971-3-youngjun.park@lge.com
Signed-off-by: Youngjun Park <youngjun.park@lge.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Barry Song <baohua@kernel.org>
Acked-by: Kairui Song <kasong@tencent.com>
Reviewed-by: Baoquan He <baoquan.he@linux.dev>
Reviewed-by: Nhat Pham <nphamcs@gmail.com>
Cc: Chris Li <chrisl@kernel.org>
Cc: Kemeng Shi <shikemeng@huaweicloud.com>
---
mm/swapfile.c | 43 ++++++++++++++++++++++++++++++-------------
1 file changed, 30 insertions(+), 13 deletions(-)
--- a/mm/swapfile.c~mm-swap-scan-by-cluster-in-find_next_to_unuse
+++ a/mm/swapfile.c
@@ -370,8 +370,6 @@ static void discard_swap_cluster(struct
}
}
-#define LATENCY_LIMIT 256
-
static inline bool cluster_is_empty(struct swap_cluster_info *info)
{
return info->count == 0;
@@ -2726,7 +2724,9 @@ unlock:
static unsigned int find_next_to_unuse(struct swap_info_struct *si,
unsigned int prev)
{
- unsigned int i;
+ struct swap_cluster_info *ci;
+ unsigned long i, end;
+ unsigned int ci_off;
unsigned long swp_tb;
/*
@@ -2735,19 +2735,36 @@ static unsigned int find_next_to_unuse(s
* hits are okay, and sys_swapoff() has already prevented new
* allocations from this area (while holding swap_lock).
*/
- for (i = prev + 1; i < si->max; i++) {
- swp_tb = swap_table_get(__swap_offset_to_cluster(si, i),
- i % SWAPFILE_CLUSTER);
- if (!swp_tb_is_null(swp_tb) && !swp_tb_is_bad(swp_tb))
- break;
- if ((i % LATENCY_LIMIT) == 0)
+ i = prev + 1;
+ while (i < si->max) {
+ ci = __swap_offset_to_cluster(si, i);
+ end = min_t(unsigned long,
+ ALIGN_DOWN(i, SWAPFILE_CLUSTER) + SWAPFILE_CLUSTER,
+ si->max);
+
+ /*
+ * An empty cluster has no slot in use, so skip it whole.
+ * A slot is uncounted only after its folio left the swap
+ * cache, so there is nothing here for try_to_unuse() to act on.
+ * Count only drops here, so a READ_ONCE() without ci->lock is
+ * enough, unlike in every other cluster_is_empty() caller.
+ */
+ if (!READ_ONCE(ci->count)) {
+ i = end;
cond_resched();
- }
+ continue;
+ }
- if (i == si->max)
- i = 0;
+ ci_off = i % SWAPFILE_CLUSTER;
+ for (; i < end; ci_off++, i++) {
+ swp_tb = swap_table_get(ci, ci_off);
+ if (!swp_tb_is_null(swp_tb) && !swp_tb_is_bad(swp_tb))
+ return i;
+ }
+ cond_resched();
+ }
- return i;
+ return 0;
}
static int try_to_unuse(unsigned int type)
_
Patches currently in -mm which might be from youngjun.park@lge.com are
reply other threads:[~2026-10-09 5:57 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=20261009055756.A76061F000FF@smtp.kernel.org \
--to=akpm@linux-foundation.org \
--cc=baohua@kernel.org \
--cc=baoquan.he@linux.dev \
--cc=chrisl@kernel.org \
--cc=kasong@tencent.com \
--cc=mm-commits@vger.kernel.org \
--cc=nphamcs@gmail.com \
--cc=shikemeng@huaweicloud.com \
--cc=youngjun.park@lge.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.