mm: drop redundant lru_add_drain in anon folio reuse paths
Subsystems (2)
v2 cover mm: drop redundant lru_add_drain in anon folio reuse paths
We are doing a large number of redundant lru_add_drain() calls in both wp_can_reuse_anon_folio() and do_swap_page(), leading to LRU lock contention and unnecessary overhead.
In wp_can_reuse_anon_folio(), we can check the refcount against the lru_cache before deciding to drain. In do_swap_page(), the drain is now entirely redundant after Kairui's work to route SYNC I/O through the swapcache in the same way as ASYNC I/O.
Build the kernel within a 1GB memcg using 20 threads with zRAM swap. The number of lru_add_drain() calls is reduced from 276,787 to 230,283, while sys time decreases slightly from 3m40.125s to 3m37.128s.
Build the kernel within an 800MB memcg using 20 threads with zRAM swap. The number of lru_add_drain() calls is reduced from 796,661 to 537,262, while sys time decreases slightly from 6m25.981s to 6m22.678s.
-v2:
* collect the reviewed-by and acked-by tags from Usama, Baoquan, Shakeel, Kairui, thanks!
* add patch4 to free swapcache for non-LRU folios, as suggested by Kairui, thanks!
-RFC:
https://lore.kernel.org/linux-mm/[email protected]/
Barry Song (Xiaomi) (4):
mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
mm: drop stale folio_ref_count()==1 check in do_swap_page reuse logic
mm: entirely remove lru_add_drain in do_swap_page
mm: try to free swapcache for non-LRU folios
mm/memory.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-)
Signature · 1 line
2.39.3 (Apple Git-146)
PATCH v2 1/4 mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
We always unconditionally drain the LRU before retrying anon folio reuse in wp_can_reuse_anon_folio(). Instead, assume !LRU anon folios are in lru_cache, and use the refcount to avoid many unnecessary LRU drains.
--- mm/memory.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
mm/memory.c +7 -1
@@ -4193,12 +4193,18 @@ static bool wp_can_reuse_anon_folio(struct folio *folio,*/ if (folio_test_ksm (folio) ||folio_ref_count (folio) >3 )return false; -if (!folio_test_lru (folio)) +if (!folio_test_lru (folio)) { +/* +* Assume folio is on lru_cache and holds a cache reference. +*/ +if (folio_ref_count (folio) >2 +folio_test_swapcache (folio)) +return false;/* * We cannot easily detect+handle references from * remote LRU caches or references to LRU folios. */ lru_add_drain (); + }if (folio_ref_count (folio) >1 +folio_test_swapcache (folio))return false;if (!folio_trylock (folio))
Signature · 1 line
2.39.3 (Apple Git-146)
Re: [PATCH v2 1/4] mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
Quoted 38 lines · Barry Song (Xiaomi)
We always unconditionally drain the LRU before retrying anon folio reuse in
wp_can_reuse_anon_folio(). Instead, assume !LRU anon folios are inlru_cache, and use the refcount to avoid many unnecessary LRU drains.Acked-by Shakeel Butt <[email protected]>Reviewed-by Baoquan He <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)mm/memory.c +7
-2@@ -4193,12 +4193,18 @@ static bool wp_can_reuse_anon_folio(struct folio *folio,*/ if (folio_test_ksm (folio) ||folio_ref_count (folio) >3 )return false; -if (!folio_test_lru (folio)) +if (!folio_test_lru (folio)) { +/* +* Assume folio is on lru_cache and holds a cache reference. +*/ +if (folio_ref_count (folio) >2 +folio_test_swapcache (folio)) +return false;/* * We cannot easily detect+handle references from * remote LRU caches or references to LRU folios. */ lru_add_drain (); + }if (folio_ref_count (folio) >1 +folio_test_swapcache (folio))return false;if (!folio_trylock (folio)) --
Quoted 2 lines
2.39.3 (Apple Git-146)
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 1/4] mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
In reply to Barry Song (Xiaomi)Quoted 17 lines · Barry Song (Xiaomi)
We always unconditionally drain the LRU before retrying anon folio reuse in
wp_can_reuse_anon_folio(). Instead, assume !LRU anon folios are inlru_cache, and use the refcount to avoid many unnecessary LRU drains.Acked-by Shakeel Butt <[email protected]>Reviewed-by Baoquan He <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)mm/memory.c +0
-0
mm/memory.c static bool wp_can_reuse_anon_folio(struct folio *folio, · line 4201
Quoted 10 lines
mm/memory.c +6
-1@@ -4193,12 +4193,18 @@ static bool wp_can_reuse_anon_folio(struct folio *folio,*/ if (folio_test_ksm (folio) ||folio_ref_count (folio) >3 )return false; -if (!folio_test_lru (folio)) +if (!folio_test_lru (folio)) { +/* +* Assume folio is on lru_cache and holds a cache reference. +*/ +if (folio_ref_count (folio) >2 +folio_test_swapcache (folio)) +return false;
I'm not keen on making this function even uglier, so no, not like that.
We have the earlier "folio_ref_count(folio) > 3" check.
In which scenarios can you trigger this such that we would care?
If the answer is "I don't know" there is no reason for a change.
Signature · 3 lines
Cheers, David
Re: [PATCH v2 1/4] mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
Quoted 32 lines · David Hildenbrand (Arm)
Quoted 17 lines · Barry Song (Xiaomi)
We always unconditionally drain the LRU before retrying anon folio reuse in
wp_can_reuse_anon_folio(). Instead, assume !LRU anon folios are inlru_cache, and use the refcount to avoid many unnecessary LRU drains.Acked-by Shakeel Butt <[email protected]>Reviewed-by Baoquan He <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)mm/memory.c +0
-0mm/memory.c static bool wp_can_reuse_anon_folio(struct folio *folio, · line 4201
Quoted 10 lines
mm/memory.c +6
-1@@ -4193,12 +4193,18 @@ static bool wp_can_reuse_anon_folio(struct folio *folio,*/ if (folio_test_ksm (folio) ||folio_ref_count (folio) >3 )return false; -if (!folio_test_lru (folio)) +if (!folio_test_lru (folio)) { +/* +* Assume folio is on lru_cache and holds a cache reference. +*/ +if (folio_ref_count (folio) >2 +folio_test_swapcache (folio)) +return false;I'm not keen on making this function even uglier, so no, not like that.
Quoted 5 lines
We have the earlier "
folio_ref_count(folio)> 3" check.In which scenarios can you trigger this such that we would care?
If the answer is "I don't know" there is no reason for a change.
As I replied to Shakeel in the v1 discussion [1], this can avoid a large number of drains, both during Ubuntu boot and under normal workloads:
"I booted the system into Ubuntu, and after
boot completed I observed:
wp_reuse_skipped_drain: 5542do_swap_skipped_drain: 0
Then I built the kernel in a 1GB memcg using zRAM swap, and observed:
wp_reuse_skipped_drain: 25017do_swap_skipped_drain: 43595
So in summary, even without swap-in we can save a significant number of drains in wp_can_reuse_anon_folio(). With heavy swap-in workloads, most of the savings come from avoiding drains in do_swap_page(), while we still see a substantial number of skipped drains in wp_can_reuse_anon_folio()."
This is exactly the case where folio_ref_count(folio) == 3 and !folio_test_swapcache(folio). That is why checking folio_ref_count(folio) > 3 does not help.
[1] https://lore.kernel.org/linux-mm/CAGsJ_4yCeAmpfo_8ef4MrRyRD_UtV-xGm6F4GpSjvLVPQsbiEg@mail.gmail.com/
Best Regards
Barry
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 1/4] mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
Quoted 38 lines · Barry Song
Quoted 36 lines · David Hildenbrand (Arm)
Quoted 17 lines · Barry Song (Xiaomi)
We always unconditionally drain the LRU before retrying anon folio reuse in
wp_can_reuse_anon_folio(). Instead, assume !LRU anon folios are inlru_cache, and use the refcount to avoid many unnecessary LRU drains.Acked-by Shakeel Butt <[email protected]>Reviewed-by Baoquan He <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)mm/memory.c +0
-0mm/memory.c static bool wp_can_reuse_anon_folio(struct folio *folio, · line 4201
Quoted 10 lines
mm/memory.c +6
-1@@ -4193,12 +4193,18 @@ static bool wp_can_reuse_anon_folio(struct folio *folio,*/ if (folio_test_ksm (folio) ||folio_ref_count (folio) >3 )return false; -if (!folio_test_lru (folio)) +if (!folio_test_lru (folio)) { +/* +* Assume folio is on lru_cache and holds a cache reference. +*/ +if (folio_ref_count (folio) >2 +folio_test_swapcache (folio)) +return false;I'm not keen on making this function even uglier, so no, not like that.
We have the earlier "
folio_ref_count(folio)> 3" check.In which scenarios can you trigger this such that we would care?
Quoted 7 lines
Quoted 1 line
If the answer is "I don't know" there is no reason for a change.
As I replied to Shakeel in the v1 discussion [1], this can avoid a large number of drains, both during Ubuntu boot and under normal workloads:
"I booted the system into Ubuntu, and after
is this with this patch only?
Quoted 10 lines
boot completed I observed:
wp_reuse_skipped_drain: 5542do_swap_skipped_drain: 0Then I built the kernel in a 1GB memcg using zRAM swap, and observed:
wp_reuse_skipped_drain: 25017do_swap_skipped_drain: 43595
This is all data that belongs into this patch description, not hidden somewhere on the internet :)
And why do we care about local draining? This is not a drain-all.
Really, this patch needs more motivation clearly spelled out.
Quoted 9 lines
So in summary, even without swap-in we can save a significant number of drains in
wp_can_reuse_anon_folio(). With heavy swap-in workloads, most of the savings come from avoiding drains indo_swap_page(), while we still see a substantial number of skipped drains inwp_can_reuse_anon_folio()."This is exactly the case where
folio_ref_count(folio)== 3 and !folio_test_swapcache(folio). That is why checkingfolio_ref_count(folio)> 3 does not help.
mm/memory.c +7 -3
@@ -4181,6 +4181,9 @@ static bool __wp_can_reuse_large_anon_folio(struct folio *folio,static bool wp_can_reuse_anon_folio (struct folio *folio,struct vm_area_struct *vma) { +const bool in_lru_cache = !folio_test_lru (folio); +const bool in_swapcache =folio_test_swapcache (folio); +if (IS_ENABLED (CONFIG_TRANSPARENT_HUGEPAGE ) &&folio_test_large (folio))return __wp_can_reuse_large_anon_folio (folio, vma);
@@ -4191,15 +4194,16 @@ static bool wp_can_reuse_anon_folio(struct folio *folio,* * KSM doesn't necessarily raise the folio refcount. */ -if (folio_test_ksm (folio) ||folio_ref_count (folio) >3 ) +if (folio_test_ksm (folio) || +folio_ref_count (folio) >1 + in_lru_cache + in_swapcache)return false; -if (!folio_test_lru (folio)) +if (in_lru_cache)/* * We cannot easily detect+handle references from * remote LRU caches or references to LRU folios. */ lru_add_drain (); -if (folio_ref_count (folio) >1 +folio_test_swapcache (folio)) +if (folio_ref_count (folio) >1 + in_swapcache)return false;if (!folio_trylock (folio))return false;
?
But I also want to hear why we care about the local draining.
Signature · 3 lines
Cheers, David
Re: [PATCH v2 1/4] mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
Quoted 55 lines · David Hildenbrand (Arm)
Quoted 38 lines · Barry Song
Quoted 36 lines · David Hildenbrand (Arm)
Quoted 17 lines · Barry Song (Xiaomi)
We always unconditionally drain the LRU before retrying anon folio reuse in
wp_can_reuse_anon_folio(). Instead, assume !LRU anon folios are inlru_cache, and use the refcount to avoid many unnecessary LRU drains.Acked-by Shakeel Butt <[email protected]>Reviewed-by Baoquan He <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)mm/memory.c +0
-0mm/memory.c static bool wp_can_reuse_anon_folio(struct folio *folio, · line 4201
Quoted 10 lines
mm/memory.c +6
-1@@ -4193,12 +4193,18 @@ static bool wp_can_reuse_anon_folio(struct folio *folio,*/ if (folio_test_ksm (folio) ||folio_ref_count (folio) >3 )return false; -if (!folio_test_lru (folio)) +if (!folio_test_lru (folio)) { +/* +* Assume folio is on lru_cache and holds a cache reference. +*/ +if (folio_ref_count (folio) >2 +folio_test_swapcache (folio)) +return false;I'm not keen on making this function even uglier, so no, not like that.
We have the earlier "
folio_ref_count(folio)> 3" check.In which scenarios can you trigger this such that we would care?
Quoted 7 lines
Quoted 1 line
If the answer is "I don't know" there is no reason for a change.
As I replied to Shakeel in the v1 discussion [1], this can avoid a large number of drains, both during Ubuntu boot and under normal workloads:
"I booted the system into Ubuntu, and after
is this with this patch only?
Quoted 6 lines
boot completed I observed:
wp_reuse_skipped_drain: 5542do_swap_skipped_drain: 0
Quoted 7 lines
Quoted 4 lines
Then I built the kernel in a 1GB memcg using zRAM swap, and observed:
wp_reuse_skipped_drain: 25017do_swap_skipped_drain: 43595This is all data that belongs into this patch description, not hidden somewhere on the internet :)
I asked the same i.e. to include this information in the commit message [1].
[1] https://lore.kernel.org/linux-mm/[email protected]/
Quoted 3 lines
And why do we care about local draining? This is not a drain-all.
Let me take a stab at it. Local draining can potentially make the lru cache's batching ineffective. The whole reason for lru caches is to batch the lru operations to keep the lru lock contention low but if we keep draining prematurely we will continue to make lru lock contention worse. This is particularly bad for workloads which chrun a lot of LRU pages through allocation, free and/or reclaim.
Re: [PATCH v2 1/4] mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
Quoted 43 lines · Shakeel Butt
Quoted 41 lines · David Hildenbrand (Arm)
Quoted 40 lines · Barry Song
Quoted 37 lines · David Hildenbrand (Arm)
Quoted 17 lines · Barry Song (Xiaomi)
We always unconditionally drain the LRU before retrying anon folio reuse in
wp_can_reuse_anon_folio(). Instead, assume !LRU anon folios are inlru_cache, and use the refcount to avoid many unnecessary LRU drains.Acked-by Shakeel Butt <[email protected]>Reviewed-by Baoquan He <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)mm/memory.c +0
-0mm/memory.c static bool wp_can_reuse_anon_folio(struct folio *folio, · line 4201
Quoted 10 lines
mm/memory.c +6
-1@@ -4193,12 +4193,18 @@ static bool wp_can_reuse_anon_folio(struct folio *folio,*/ if (folio_test_ksm (folio) ||folio_ref_count (folio) >3 )return false; -if (!folio_test_lru (folio)) +if (!folio_test_lru (folio)) { +/* +* Assume folio is on lru_cache and holds a cache reference. +*/ +if (folio_ref_count (folio) >2 +folio_test_swapcache (folio)) +return false;I'm not keen on making this function even uglier, so no, not like that.
We have the earlier "
folio_ref_count(folio)> 3" check.In which scenarios can you trigger this such that we would care?
If the answer is "I don't know" there is no reason for a change.
Quoted 7 lines
Quoted 7 lines
Quoted 5 lines
As I replied to Shakeel in the v1 discussion [1], this can avoid a large number of drains, both during Ubuntu boot and under normal workloads:
"I booted the system into Ubuntu, and after
is this with this patch only?
Right. wp_reuse_skipped_drain accounts for the behavior introduced by this patch, while do_swap_skipped_drain comes from patch 3/3.
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
boot completed I observed:
wp_reuse_skipped_drain: 5542do_swap_skipped_drain: 0
Quoted 7 lines
Quoted 7 lines
Quoted 4 lines
Then I built the kernel in a 1GB memcg using zRAM swap, and observed:
wp_reuse_skipped_drain: 25017do_swap_skipped_drain: 43595This is all data that belongs into this patch description, not hidden somewhere on the internet :)
Agreed. Sorry, I was being a bit lazy.
Quoted 4 lines
I asked the same i.e. to include this information in the commit message [1].
Sorry, I missed this one. I'll add it in v3.
Quoted 11 lines
Quoted 4 lines
And why do we care about local draining? This is not a drain-all.
Let me take a stab at it. Local draining can potentially make the lru cache's batching ineffective. The whole reason for lru caches is to batch the lru operations to keep the lru lock contention low but if we keep draining prematurely we will continue to make lru lock contention worse. This is particularly bad for workloads which chrun a lot of LRU pages through allocation, free and/or reclaim.
I actually asked the same question myself in the RFC here[1]:
"On the other hand, the folio may be sitting in the lru_cache of another CPU, which the current drain cannot flush. As things stand today, the drain only succeeds if the folio happens to be queued on the current CPU's lru_cache, giving it roughly a 1/nr_cpus chance of working. drain_all would clearly be too expensive to avoid. So another possibility is to drop this drain as well. The folio can be released later, at the cost of missing some opportunities for reuse."
After thinking about it for a couple of days, my gut feeling is that keeping it might increase the chances of reuse in at least two cases:
1. It could be a newly allocated folio in do_swap_page(), and do_swap_page() may subsequently call do_wp_page(). In that case, task migration is less likely to have occurred.
2. On systems with fewer CPUs (and typically less memory), the chance of having the folio in the same CPU's lru_cache, which is roughly 1 / nr_cpus, may still be reasonably high.
I'm not entirely sure about this, so I chose a relatively conservative approach: making an obviously correct improvement without introducing dramatic changes. Another reason is that it's also hard to evaluate the impact of removing the local drain across all systems and workloads.
Shakeel, David, what are your thoughts on this? Should we go with a code refinement in v3 as David suggested, or completely remove this drain?
[1] https://lore.kernel.org/linux-mm/CAGsJ_4yFgcvpTAZ_YV73+U=BEnOiSxaFtGNFpbf0DWBkVcOQ3Q@mail.gmail.com/
Best Regards
Barry
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 1/4] mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
Quoted 63 lines · Barry Song
Quoted 4 lines · Shakeel Butt
Quoted 2 lines · David Hildenbrand (Arm)
is this with this patch only?
Right.
wp_reuse_skipped_drainaccounts for the behavior introduced by this patch, whiledo_swap_skipped_draincomes from patch 3/3.Quoted 4 lines
Quoted 4 lines
This is all data that belongs into this patch description, not hidden somewhere on the internet :)
Agreed. Sorry, I was being a bit lazy.
Quoted 4 lines
I asked the same i.e. to include this information in the commit message [1].
Sorry, I missed this one. I'll add it in v3.
Quoted 11 lines
Quoted 4 lines
And why do we care about local draining? This is not a drain-all.
Let me take a stab at it. Local draining can potentially make the lru cache's batching ineffective. The whole reason for lru caches is to batch the lru operations to keep the lru lock contention low but if we keep draining prematurely we will continue to make lru lock contention worse. This is particularly bad for workloads which chrun a lot of LRU pages through allocation, free and/or reclaim.
I actually asked the same question myself in the RFC here[1]:
"On the other hand, the folio may be sitting in the
lru_cacheof another CPU, which the current drain cannot flush. As things stand today, the drain only succeeds if the folio happens to be queued on the current CPU'slru_cache, giving it roughly a 1/nr_cpus chance of working.drain_allwould clearly be too expensive to avoid. So another possibility is to drop this drain as well. The folio can be released later, at the cost of missing some opportunities for reuse."After thinking about it for a couple of days, my gut feeling is that keeping it might increase the chances of reuse in at least two cases:
1. It could be a newly allocated folio indo_swap_page(), anddo_swap_page()may subsequently calldo_wp_page(). In that case, task migration is less likely to have occurred.
2. On systems with fewer CPUs (and typically less memory), the chance of having the folio in the same CPU'slru_cache, which is roughly 1 /nr_cpus, may still be reasonably high.I'm not entirely sure about this, so I chose a relatively conservative approach: making an obviously correct improvement without introducing dramatic changes. Another reason is that it's also hard to evaluate the impact of removing the local drain across all systems and workloads.
Quoted 3 lines
Shakeel, David, what are your thoughts on this? Should we go with a code refinement in v3 as David suggested, or completely remove this drain?
When we added that code, we didn't have PageAnonExclusive yet. I documented that in:
commit d4c470970d45c863fafc757521a82be2f80b1232
Author: David Hildenbrand <[email protected]> Date: Thu Mar 24 18:13:34 2022 -0700
mm: optimize do_wp_page() for fresh pages in local LRU pagevecs
For example, if a page just got swapped in via a read fault, the LRU pagevecs might still hold a reference to the page. If we trigger a write fault on such a page, the additional reference from the LRU pagevecs will prohibit reusing the page.
Let's conditionally drain the local LRU pagevecs when we stumble over a !PageLRU() page. We cannot easily drain remote LRU pagevecs and it might not be desirable performance-wise. Consequently, this will only avoid copying in some cases.
Add a simple "page_count(page) > 3" check first but keep the "page_count(page) > 1 + PageSwapCache(page)" check in place, as we want to minimize cases where we remove a page from the swapcache but won't be able to reuse it, for example, because another process has it mapped R/O, to not affect reclaim.
We cannot easily handle the following cases and we will always have to copy:
(1) The page is referenced in the LRU pagevecs of other CPUs. We really would have to drain the LRU pagevecs of all CPUs -- most probably copying is much cheaper.
(2) The page is already PageLRU() but is getting moved between LRU lists, for example, for activation (e.g., mark_page_accessed()), deactivation (MADV_COLD), or lazyfree (MADV_FREE). We'd have to drain mostly unconditionally, which might be bad performance-wise. Most probably this won't happen too often in practice.
Note that there are other reasons why an anon page might temporarily not be PageLRU(): for example, compaction and migration have to isolate LRU pages from the LRU lists first (isolate_lru_page()), moving them to temporary local lists and clearing PageLRU() and holding an additional reference on the page. In that case, we'll always copy.
This change seems to be fairly effective with the reproducer [1] shared by Nadav, as long as writeback is done synchronously, for example, using zram. However, with asynchronous writeback, we'll usually fail to free the swapcache because the page is still under writeback: something we cannot easily optimize for, and maybe it's not really relevant in practice.
[1] https://lkml.kernel.org/r/[email protected]
With PAE, this case would now be handled differently in the simple alloc+swapin scenario. Throw in a fork()+execve() in between and the optimization would still matter.
So yeah, if Nadav's reproducer works even without the drain (which I assume), we could indeed think about just removing this optimization here.
Signature · 3 lines
Cheers, David
Re: [PATCH v2 1/4] mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
On Mon, Jun 29, 2026 at 3:52 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]
Quoted 3 lines
So yeah, if Nadav's reproducer works even without the drain (which I assume), we could indeed think about just removing this optimization here.
Yet I think the problem is resolved with the exclusive flag. Right now, we can still have cases where "exclusive" cannot address it, such as:
#include <sys/mman.h>
int main(int argc, char *argv[])
{
int i;
#define SIZE 30*1024*1024
while(1) {
volatile int *p = mmap(0, SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
volatile int q;
for (int i = 0; i < SIZE/sizeof(int); i++)
p[i] = i;
madvise((void *)p, SIZE, MADV_PAGEOUT);
if (!fork())
_exit(0);
for (int i = 0; i < SIZE/sizeof(int); i++)
q = p[i];
for (int i = 0; i < SIZE/sizeof(int); i++)
p[i] = i;
munmap(p, SIZE);
}
return 0;
}I assume this is less frequent and less of a concern?
Thanks
Barry
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 1/4] mm: avoid unnecessary lru drain for wp_can_reuse_anon_folio()
Quoted 36 lines · Barry Song
On Mon, Jun 29, 2026 at 3:52 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]Quoted 3 lines
So yeah, if Nadav's reproducer works even without the drain (which I assume), we could indeed think about just removing this optimization here.
Yet I think the problem is resolved with the exclusive flag. Right now, we can still have cases where "exclusive" cannot address it, such as:
#include <
sys/mman.h>int
main(int argc, char *argv[]){ int i; #define SIZE 30*1024*1024 while(1) { volatile int *p = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); volatile int q; for (int i = 0; i < SIZE/sizeof(int); i++) p[i] = i; madvise((void *)p, SIZE, MADV_PAGEOUT); if (!fork()) _exit(0); for (int i = 0; i < SIZE/sizeof(int); i++) q = p[i]; for (int i = 0; i < SIZE/sizeof(int); i++) p[i] = i; munmap(p, SIZE); } return 0; }
Quoted 1 line
I assume this is less frequent and less of a concern?
It's unlikely to matter in practice I guess.
Signature · 3 lines
Cheers, David
PATCH v2 2/4 mm: drop stale folio_ref_count()==1 check in do_swap_page reuse logic
In reply to Barry Song (Xiaomi)The "we just allocated them without exposing them to the swapcache" case no longer exists, as Kairui has routed synchronous I/O through the swapcache as well in his series "unify swapin use swap cache and cleanup flags"[1]. As a result, folio_ref_count() should never be 1 in this path, since at least two references are held (base ref plus swapcache). Remove the folio_ref_count()==1 check and update the comment accordingly.
[1] https://lore.kernel.org/all/[email protected]/
--- mm/memory.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
mm/memory.c +2 -5
@@ -5049,12 +5049,9 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)/* * Same logic as in do_wp_page(); however, optimize for pages that are -* certainly not shared either because we just allocated them without -* exposing them to the swapcache or because the swap entry indicates -* exclusivity. +* certainly not because the swap entry indicates exclusivity. */ -if (!folio_test_ksm (folio) && - (exclusive ||folio_ref_count (folio) ==1 )) { +if (!folio_test_ksm (folio) && exclusive) {if ((vma->vm_flags &VM_WRITE ) && !userfaultfd_pte_wp (vma, pte) && !pte_needs_soft_dirty_wp (vma, pte)) { pte =pte_mkwrite (pte, vma);
Signature · 1 line
2.39.3 (Apple Git-146)
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 2/4] mm: drop stale folio_ref_count()==1 check in do_swap_page reuse logic
Quoted 24 lines · Barry Song (Xiaomi)
The "we just allocated them without exposing them to the swapcache" case no longer exists, as Kairui has routed synchronous I/O through the swapcache as well in his series "unify swapin use swap cache and cleanup flags"[1]. As a result,
folio_ref_count()should never be 1 in this path, since at least two references are held (base ref plus swapcache). Remove the folio_ref_count()==1 check and update the comment accordingly.[1] https://lore.kernel.org/all/[email protected]/
Acked-by Usama Arif <[email protected]>Reviewed-by Kairui Song <[email protected]>Reviewed-by Baoquan He <[email protected]>Acked-by Shakeel Butt <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)mm/memory.c +0
-0
mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5051
Quoted 3 lines
mm/memory.c +0
-0@@ -5049,12 +5049,9 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)/* * Same logic as in do_wp_page(); however, optimize for pages that are
s/Same/Similar/ ?
mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5054
Quoted 8 lines
mm/memory.c +2
-5-* certainly not shared either because we just allocated them without -* exposing them to the swapcache or because the swap entry indicates -* exclusivity. +* certainly not because the swap entry indicates exclusivity. */ -if (!folio_test_ksm (folio) && - (exclusive ||folio_ref_count (folio) ==1 )) { +if (!folio_test_ksm (folio) && exclusive) {
Hmm, but KSM folios should never have "exclusive" set. So I think you can drop that as well (was only relevant with folio_ref_count==1 check IIRC).
Signature · 3 lines
Cheers, David
Re: [PATCH v2 2/4] mm: drop stale folio_ref_count()==1 check in do_swap_page reuse logic
Quoted 27 lines · David Hildenbrand (Arm)
Quoted 25 lines · Barry Song (Xiaomi)
The "we just allocated them without exposing them to the swapcache" case no longer exists, as Kairui has routed synchronous I/O through the swapcache as well in his series "unify swapin use swap cache and cleanup flags"[1]. As a result,
folio_ref_count()should never be 1 in this path, since at least two references are held (base ref plus swapcache). Remove the folio_ref_count()==1 check and update the comment accordingly.[1] https://lore.kernel.org/all/[email protected]/
Acked-by Usama Arif <[email protected]>Reviewed-by Kairui Song <[email protected]>Reviewed-by Baoquan He <[email protected]>Acked-by Shakeel Butt <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)mm/memory.c +0
-0@@ -5049,12 +5049,9 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
Quoted 4 lines
mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5051
Quoted 2 lines
mm/memory.c +0
-0/* * Same logic as in do_wp_page(); however, optimize for pages that are s/Same/Similar/ ?
will change to similar in v3.
Quoted 10 lines
mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5054
Quoted 9 lines
mm/memory.c +2
-5-* certainly not shared either because we just allocated them without -* exposing them to the swapcache or because the swap entry indicates -* exclusivity. +* certainly not because the swap entry indicates exclusivity. */ -if (!folio_test_ksm (folio) && - (exclusive ||folio_ref_count (folio) ==1 )) { +if (!folio_test_ksm (folio) && exclusive) {
Quoted 2 lines
Hmm, but KSM folios should never have "exclusive" set. So I think you can drop that as well (was only relevant with folio_ref_count==1 check IIRC).
right. will drop !folio_test_ksm(folio) in v3.
Best Regards
Barry
PATCH v2 3/4 mm: entirely remove lru_add_drain in do_swap_page
In reply to Barry Song (Xiaomi)We are doing a lot of redundant lru_add_drain() calls in do_swap_page(), especially for synchronous I/O devices. For example, the test program below currently ends up draining lru_cache 100% of the time:
int main(int argc, char *argv[])
{
int i;
#define SIZE 100*1024*1024
while(1) {
volatile int *p = mmap(0, SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
for (int i = 0; i < SIZE/sizeof(int); i++)
p[i] = i%64;
madvise((void *)p, SIZE, MADV_PAGEOUT);
for (int i = 0; i < SIZE/sizeof(int); i++)
p[i] = i%64;
munmap(p, SIZE);
}
return 0;
}Folio reuse now relies primarily on the exclusive hint, making lru_cache draining to drop the refcount in lru_cache largely irrelevant.
--- mm/memory.c | 10 ---------- 1 file changed, 10 deletions(-)
mm/memory.c +0 -10
@@ -4903,16 +4903,6 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) }else if (folio != swapcache) page =folio_page (folio,0 ); -/* -* If we want to map a page that's in the swapcache writable, we -* have to detect via the refcount if we're really the exclusive -* owner. Try removing the extra reference from the local LRU -* caches if required. -*/ -if ((vmf->flags &FAULT_FLAG_WRITE ) && - !folio_test_ksm (folio) && !folio_test_lru (folio)) -lru_add_drain (); -folio_throttle_swaprate (folio,GFP_KERNEL );/*
Signature · 1 line
2.39.3 (Apple Git-146)
Re: [PATCH v2 3/4] mm: entirely remove lru_add_drain in do_swap_page
Quoted 57 lines · Barry Song (Xiaomi)
We are doing a lot of redundant
lru_add_drain()calls indo_swap_page(), especially for synchronous I/O devices. For example, the test program below currently ends up draininglru_cache100% of the time:int
main(int argc, char *argv[]){ int i; #define SIZE 100*1024*1024 while(1) { volatile int *p = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); for (int i = 0; i < SIZE/sizeof(int); i++) p[i] = i%64; madvise((void *)p, SIZE, MADV_PAGEOUT); for (int i = 0; i < SIZE/sizeof(int); i++) p[i] = i%64; munmap(p, SIZE); } return 0; }Folio reuse now relies primarily on the exclusive hint, making
lru_cachedraining to drop the refcount inlru_cachelargely irrelevant.Acked-by Shakeel Butt <[email protected]>Reviewed-by Baoquan He <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 10 ---------- 1 file changed, 10 deletions(-)mm/memory.c +0
-11@@ -4903,16 +4903,6 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) }else if (folio != swapcache) page =folio_page (folio,0 ); -/* -* If we want to map a page that's in the swapcache writable, we -* have to detect via the refcount if we're really the exclusive -* owner. Try removing the extra reference from the local LRU -* caches if required. -*/ -if ((vmf->flags &FAULT_FLAG_WRITE ) && - !folio_test_ksm (folio) && !folio_test_lru (folio)) -lru_add_drain (); -folio_throttle_swaprate (folio,GFP_KERNEL );/* --
Quoted 2 lines
2.39.3 (Apple Git-146)
Thanks, I saw the previous discussed problem is address in the next patch, it's totally fine so:
Re: [PATCH v2 3/4] mm: entirely remove lru_add_drain in do_swap_page
In reply to Barry Song (Xiaomi)Quoted 23 lines · Barry Song (Xiaomi)
We are doing a lot of redundant
lru_add_drain()calls indo_swap_page(), especially for synchronous I/O devices. For example, the test program below currently ends up draininglru_cache100% of the time:int
main(int argc, char *argv[]){ int i; #define SIZE 100*1024*1024 while(1) { volatile int *p = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); for (int i = 0; i < SIZE/sizeof(int); i++) p[i] = i%64; madvise((void *)p, SIZE, MADV_PAGEOUT); for (int i = 0; i < SIZE/sizeof(int); i++) p[i] = i%64; munmap(p, SIZE); } return 0; }
Quoted 3 lines
Folio reuse now relies primarily on the exclusive hint, making
lru_cachedraining to drop the refcount inlru_cachelargely irrelevant.
Makes sense, we'll fallback to do_wp_page() where we handle the non-exclusive either way.
Signature · 3 lines
Cheers, David
PATCH v2 4/4 mm: try to free swapcache for non-LRU folios
In reply to Barry Song (Xiaomi)Originally, we unconditionally called lru_add_drain() for write swap-in page faults. This might drop the reference held by the per-CPU LRU cache if the folio happened to reside there. However, there was no guarantee that the folio was actually cached on the current CPU.
Now that lru_add_drain() has been removed, we have lost one opportunity to drop a reference held by the LRU cache. We could instead incorporate that possibility into the condition evaluated by should_try_to_free_swap().
--- mm/memory.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
mm/memory.c +4 -1
@@ -5087,8 +5087,11 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)* Remove the swap entry and conditionally try to free up the swapcache. * Do it after mapping, so raced page faults will likely see the folio * in swap cache and wait on the folio lock. +* Assume non-LRU folios may be queued in the LRU cache, which contributes +* an additional reference to the folio. */ -if (should_try_to_free_swap (si, folio, vma, nr_pages, vmf->flags)) +if (should_try_to_free_swap (si, folio, vma, nr_pages + + !folio_test_lru (folio), vmf->flags))folio_free_swap (folio);folio_unlock (folio);
Signature · 1 line
2.39.3 (Apple Git-146)
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
Quoted 23 lines · Barry Song (Xiaomi)
Originally, we unconditionally called
lru_add_drain()for write swap-in page faults. This might drop the reference held by the per-CPU LRU cache if the folio happened to reside there. However, there was no guarantee that the folio was actually cached on the current CPU.Now that
lru_add_drain()has been removed, we have lost one opportunity to drop a reference held by the LRU cache. We could instead incorporate that possibility into the condition evaluated byshould_try_to_free_swap().Suggested-by Kairui Song <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)mm/memory.c +0
-0@@ -5087,8 +5087,11 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)* Remove the swap entry and conditionally try to free up the swapcache. * Do it after mapping, so raced page faults will likely see the folio
mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5097
Quoted 10 lines
mm/memory.c +4
-1* in swap cache and wait on the folio lock. +* Assume non-LRU folios may be queued in the LRU cache, which contributes +* an additional reference to the folio. */ -if (should_try_to_free_swap (si, folio, vma, nr_pages, vmf->flags)) +if (should_try_to_free_swap (si, folio, vma, nr_pages + + !folio_test_lru (folio), vmf->flags))folio_free_swap (folio);folio_unlock (folio);
Hm, in wp_can_reuse_anon_folio() we'll try dropping the swapcache ourselves.
So I wonder if we still need that handling ("If we want to map a page that's in the swapcache writable, we ...") at all?
Ahh, I see the problem now:
commit 4b34f1d82c6549837b2061096dea249e881a4495
Author: Kairui Song <[email protected]> Date: Sat Dec 20 03:43:35 2025 +0800
mm, swap: free the swap cache after folio is mapped
Currently, we remove the folio from the swap cache and free the swap cache before mapping the PTE. To reduce repeated faults due to parallel swapins of the same PTE, change it to remove the folio from the swap cache after it is mapped. So new faults from the swap PTE will be much more likely to see the folio in the swap cache and wait on it.
This does not eliminate all swapin races: an ongoing swapin fault may still see an empty swap cache. That's harmless, as the PTE is changed before the swap cache is cleared, so it will just return and not trigger any repeated faults. This does help to reduce the chance.
That changed that behavior such that we *must* now always fallback to do_wp_page().
What a mess (I didn't ack)
Signature · 3 lines
Cheers, David
Re: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
Quoted 62 lines · David Hildenbrand (Arm)
Quoted 23 lines · Barry Song (Xiaomi)
Originally, we unconditionally called
lru_add_drain()for write swap-in page faults. This might drop the reference held by the per-CPU LRU cache if the folio happened to reside there. However, there was no guarantee that the folio was actually cached on the current CPU.Now that
lru_add_drain()has been removed, we have lost one opportunity to drop a reference held by the LRU cache. We could instead incorporate that possibility into the condition evaluated byshould_try_to_free_swap().Suggested-by Kairui Song <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)mm/memory.c +0
-0@@ -5087,8 +5087,11 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)* Remove the swap entry and conditionally try to free up the swapcache. * Do it after mapping, so raced page faults will likely see the folio mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5097
Quoted 10 lines
mm/memory.c +4
-1* in swap cache and wait on the folio lock. +* Assume non-LRU folios may be queued in the LRU cache, which contributes +* an additional reference to the folio. */ -if (should_try_to_free_swap (si, folio, vma, nr_pages, vmf->flags)) +if (should_try_to_free_swap (si, folio, vma, nr_pages + + !folio_test_lru (folio), vmf->flags))folio_free_swap (folio);folio_unlock (folio);Hm, in
wp_can_reuse_anon_folio()we'll try dropping the swapcache ourselves.So I wonder if we still need that handling ("If we want to map a page that's in the swapcache writable, we ...") at all?
Ahh, I see the problem now:
commit
4b34f1d82c6549837b2061096dea249e881a4495Author: Kairui Song <[email protected]> Date: Sat Dec 20 03:43:35 2025 +0800mm, swap: free the swap cache after folio is mapped
Currently, we remove the folio from the swap cache and free the swap cache before mapping the PTE. To reduce repeated faults due to parallel swapins of the same PTE, change it to remove the folio from the swap cache after it is mapped. So new faults from the swap PTE will be much more likely to see the folio in the swap cache and wait on it.
This does not eliminate all swapin races: an ongoing swapin fault may still see an empty swap cache. That's harmless, as the PTE is changed before the swap cache is cleared, so it will just return and not trigger any repeated faults. This does help to reduce the chance.
Quoted 3 lines
That changed that behavior such that we *must* now always fallback to
do_wp_page().What a mess (I didn't ack)
So it seems we need a patch before patch 4 to fix the missed swapcache freeing on write faults, because the write bit has already been cleared by the time the folio is mapped and reused?
mm/memory.c +0 -0
@@ -4752,6 +4752,7 @@ static void check_swap_exclusive(struct folio
*folio, swp_entry_t entry,
*/
vm_fault_t do_swap_page(struct vm_fault *vmf)
{
+ enum fault_flag fault_flags = vmf->flags;
struct vm_area_struct *vma = vmf->vma;
struct folio *swapcache = NULL, *folio;
struct page *page;@@ -5091,7 +5092,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
* an additional reference to the folio.
*/
if (should_try_to_free_swap(si, folio, vma, nr_pages +
- !folio_test_lru(folio), vmf->flags))
+ !folio_test_lru(folio), fault_flags))
folio_free_swap(folio);
folio_unlock(folio);Best Regards
Barry
Re: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
Quoted 64 lines · Barry Song
Quoted 62 lines · David Hildenbrand (Arm)
Quoted 23 lines · Barry Song (Xiaomi)
Originally, we unconditionally called
lru_add_drain()for write swap-in page faults. This might drop the reference held by the per-CPU LRU cache if the folio happened to reside there. However, there was no guarantee that the folio was actually cached on the current CPU.Now that
lru_add_drain()has been removed, we have lost one opportunity to drop a reference held by the LRU cache. We could instead incorporate that possibility into the condition evaluated byshould_try_to_free_swap().Suggested-by Kairui Song <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/memory.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)mm/memory.c +0
-0@@ -5087,8 +5087,11 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)* Remove the swap entry and conditionally try to free up the swapcache. * Do it after mapping, so raced page faults will likely see the folio mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5097
Quoted 10 lines
mm/memory.c +4
-1* in swap cache and wait on the folio lock. +* Assume non-LRU folios may be queued in the LRU cache, which contributes +* an additional reference to the folio. */ -if (should_try_to_free_swap (si, folio, vma, nr_pages, vmf->flags)) +if (should_try_to_free_swap (si, folio, vma, nr_pages + + !folio_test_lru (folio), vmf->flags))folio_free_swap (folio);folio_unlock (folio);Hm, in
wp_can_reuse_anon_folio()we'll try dropping the swapcache ourselves.So I wonder if we still need that handling ("If we want to map a page that's in the swapcache writable, we ...") at all?
Ahh, I see the problem now:
commit
4b34f1d82c6549837b2061096dea249e881a4495Author: Kairui Song <[email protected]> Date: Sat Dec 20 03:43:35 2025 +0800mm, swap: free the swap cache after folio is mapped
Currently, we remove the folio from the swap cache and free the swap cache before mapping the PTE. To reduce repeated faults due to parallel swapins of the same PTE, change it to remove the folio from the swap cache after it is mapped. So new faults from the swap PTE will be much more likely to see the folio in the swap cache and wait on it.
This does not eliminate all swapin races: an ongoing swapin fault may still see an empty swap cache. That's harmless, as the PTE is changed before the swap cache is cleared, so it will just return and not trigger any repeated faults. This does help to reduce the chance.
Quoted 3 lines
Quoted 3 lines
That changed that behavior such that we *must* now always fallback to
do_wp_page().What a mess (I didn't ack)
That's awkward, my bad, terribly sorry about this :(
I sincerely apologize for the oversight and the trouble this has caused.
I haven't seen any performance regression in any workload recently though, or any correctness issue, perhaps the round trip of do_wp_page wasn't that bad. It should still catch the reuse folios, just more costly than doing things in-place.
I think we should restore the original check first. We might also want to avoid dropping the swap cache if the folio will not be reused, which was discussed here:
https://lore.kernel.org/linux-mm/CAMgjq7BDfvNXdWH0cqarsujjUn3i3tDDhDkmSg01TR4h-tDorQ@mail.gmail.com/
Maybe extracting some common part into a helper can help make this cleaner.
Quoted 29 lines
So it seems we need a patch before patch 4 to fix the missed swapcache freeing on write faults, because the write bit has already been cleared by the time the folio is mapped and reused?
mm/memory.c +1
-0@@ -4752,6 +4752,7 @@ static void check_swap_exclusive(struct folio*folio, swp_entry_t entry, */ vm_fault_t do_swap_page (struct vm_fault *vmf) { +enum fault_flag fault_flags = vmf->flags;struct vm_area_struct *vma = vmf->vma;struct folio *swapcache =NULL , *folio;struct page *page;mm/memory.c +1
-1@@ -5091,7 +5092,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)* an additional reference to the folio. */ if (should_try_to_free_swap (si, folio, vma, nr_pages + - !folio_test_lru (folio), vmf->flags)) + !folio_test_lru (folio), fault_flags))folio_free_swap (folio);folio_unlock (folio);
Quoted 2 lines
Best Regards
Barry
Hi Barry,
The problem is more than that, the exclusive || folio_ref_count(folio) == 1 in do_swap_page is also ineffective now.
But that earlier commit 4b34f1d82c65 is kind of meaningless after recent refactor of swapin. It was trying to avoid redundant folio allocation and thrashing from concurrent faulting process of the same PTE, but recent swap table rework already gated that with unified folio allocation. We are freeing the swap entry first here, that is effective enough so that commit is no longer needed.
So perhaps we can first just revert it, how do you think?:
From 8009328e0782fc22365ef32474bae749beda637b Mon Sep 17 00:00:00 2001
From: Kairui Song <[email protected]>
Date: Thu, 25 Jun 2026 11:44:26 +0800
Subject: [PATCH] mm/swap: free swap cache before mapping to fix folio reuse
This is effectively a revert of commit 4b34f1d82c65 ("mm, swap: free the swap cache after folio is mapped").
That commit was trying to reduce wasted fault and folio allocation from parallel swapins of the same entry: keeping the folio in the swap cache until the PTE is installed makes a racing fault more likely to find it and wait on the folio lock, rather than allocate and charge a new folio only to discover the race and throw it away, which leads to thrashing.
That benefit is marginal now. Folio allocation and swapin are both gated on the swap entry count in the swap cache layer since commit 02d733a7ec1d ("mm, swap: unify large folio allocation"). Once the winning fault drops the entry count, a racing fault that misses the cache will fail the swap entry count check too and back off at the page table lock before allocating anything. Note there is still a tiny window with or without either patch, but it's already minimized now and should be fixed in other ways later if needed.
Meanwhile the earlier commit has a real problem, as David pointed out [1]. The swap cache reference is still held during the write fault folio reference check, so a sole-owned but non-exclusive write fault always falls back to do_wp_page() instead of being reused in place. The write-fault path of should_try_to_free_swap() is broken too as the FAULT_FLAG_WRITE flag is missing.
There is no correctness problem though, only the reuse and cleanup optimizations were lost.
So revert it. This makes the sole-owner check effective again and recovers the reuse fast path. Also slightly adjust a sanity check to be more rigorous.
--- mm/memory.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-)
mm/memory.c +19 -14
@@ -4512,7 +4512,6 @@ static vm_fault_t remove_device_exclusive_entry(struct vm_fault *vmf)static inline bool should_try_to_free_swap (struct swap_info_struct *si,struct folio *folio,struct vm_area_struct *vma, -unsigned int extra_refs,unsigned int fault_flags) {if (!folio_test_swapcache (folio))
@@ -4535,7 +4534,7 @@ static inline bool should_try_to_free_swap(struct swap_info_struct *si,* reference only in case it's likely that we'll be the exclusive user. */ return (fault_flags &FAULT_FLAG_WRITE ) && !folio_test_ksm (folio) && -folio_ref_count (folio) == (extra_refs +folio_nr_pages (folio)); +folio_ref_count (folio) == (1 +folio_nr_pages (folio)); }static vm_fault_t pte_marker_clear (struct vm_fault *vmf)
@@ -5033,6 +5032,24 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)*/ arch_swap_restore (folio_swap (entry, folio), folio); +/* +* Remove the swap entry and conditionally try to free up the swapcache. +* We're already holding a reference on the page but haven't mapped it +* yet. +* +* The swap count has to be freed to 0 first so folio_free_swap +* can free exclusive clean cache. Concurrent fault is very unlikely +* to trigger redundant folio alloc, nor will it cause redundant IO, +* as the swap entry count gates it. +*/ +VM_WARN_ON_ONCE (nr_pages !=1 && nr_pages !=folio_nr_pages (folio)); +if (folio != swapcache) +folio_put_swap (swapcache,NULL ); +else +folio_put_swap (folio, nr_pages <folio_nr_pages (folio) ? page :NULL ); +if (should_try_to_free_swap (si, folio, vma, vmf->flags)) +folio_free_swap (folio); +add_mm_counter (vma->vm_mm,MM_ANONPAGES , nr_pages);add_mm_counter (vma->vm_mm,MM_SWAPENTS , -nr_pages); pte =mk_pte (page, vma->vm_page_prot);
@@ -5067,7 +5084,6 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)if (unlikely (folio != swapcache)) {folio_add_new_anon_rmap (folio, vma, address,RMAP_EXCLUSIVE );folio_add_lru_vma (folio, vma); -folio_put_swap (swapcache,NULL ); }else if (!folio_test_anon (folio)) {/* * We currently only expect !anon folios that are fully
@@ -5076,12 +5092,9 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)VM_WARN_ON_ONCE_FOLIO (folio_nr_pages (folio) != nr_pages, folio);VM_WARN_ON_ONCE_FOLIO (folio_mapped (folio), folio);folio_add_new_anon_rmap (folio, vma, address, rmap_flags); -folio_put_swap (folio,NULL ); }else { -VM_WARN_ON_ONCE (nr_pages !=1 && nr_pages !=folio_nr_pages (folio));folio_add_anon_rmap_ptes (folio, page, nr_pages, vma, address, rmap_flags); -folio_put_swap (folio, nr_pages ==1 ? page :NULL ); } VM_BUG_ON(!folio_test_anon(folio) ||
@@ -5090,14 +5103,6 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)arch_do_swap_page_nr (vma->vm_mm, vma, address, pte, pte, nr_pages); -/* -* Remove the swap entry and conditionally try to free up the swapcache. -* Do it after mapping, so raced page faults will likely see the folio -* in swap cache and wait on the folio lock. -*/ -if (should_try_to_free_swap (si, folio, vma, nr_pages, vmf->flags)) -folio_free_swap (folio); -folio_unlock (folio);if (unlikely (folio != swapcache)) {/*
Signature · 1 line
2.54.0
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
Quoted 8 lines
Quoted 3 lines
Quoted 3 lines
That changed that behavior such that we *must* now always fallback to
do_wp_page().What a mess (I didn't ack)
That's awkward, my bad, terribly sorry about this :(
I sincerely apologize for the oversight and the trouble this has caused.
All good, I was just surprised to see a previous optimization partially reverted without a clear reasoning :)
Because it should have removed the handling in should_try_to_free_swap() as well.
It's good that we are discussing it now!
Quoted 4 lines
I haven't seen any performance regression in any workload recently though, or any correctness issue, perhaps the round trip of
do_wp_pagewasn't that bad. It should still catch the reuse folios, just more costly than doing things in-place.
Right, do_wp_page() handles it, after the page was mapped. It adds some overhead, but fortunately no TLB flush if we're just upgrading write permissions.
The optimization dates back to pre PageAnonExclusive handling.
Quoted 39 lines
I think we should restore the original check first. We might also want to avoid dropping the swap cache if the folio will not be reused, which was discussed here:
https://lore.kernel.org/linux-mm/CAMgjq7BDfvNXdWH0cqarsujjUn3i3tDDhDkmSg01TR4h-tDorQ@mail.gmail.com/
Maybe extracting some common part into a helper can help make this cleaner.
Quoted 29 lines
So it seems we need a patch before patch 4 to fix the missed swapcache freeing on write faults, because the write bit has already been cleared by the time the folio is mapped and reused?
mm/memory.c +1
-0@@ -4752,6 +4752,7 @@ static void check_swap_exclusive(struct folio*folio, swp_entry_t entry, */ vm_fault_t do_swap_page (struct vm_fault *vmf) { +enum fault_flag fault_flags = vmf->flags;struct vm_area_struct *vma = vmf->vma;struct folio *swapcache =NULL , *folio;struct page *page;mm/memory.c +1
-1@@ -5091,7 +5092,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)* an additional reference to the folio. */ if (should_try_to_free_swap (si, folio, vma, nr_pages + - !folio_test_lru (folio), vmf->flags)) + !folio_test_lru (folio), fault_flags))folio_free_swap (folio);folio_unlock (folio);
Quoted 7 lines
Quoted 2 lines
Best Regards
BarryHi Barry,
The problem is more than that, the
exclusive || folio_ref_count(folio) == 1indo_swap_pageis also ineffective now.
Exactly.
If the roundtrip through do_wp_page() is good enough today, we can just do
@@ -4512,7 +4516,6 @@ static vm_fault_t remove_device_exclusive_entry(struct vm_fault *vmf)
static inline bool should_try_to_free_swap(struct swap_info_struct *si,
struct folio *folio,
struct vm_area_struct *vma,
- unsigned int extra_refs,
unsigned int fault_flags)
{
if (!folio_test_swapcache(folio))
@@ -4528,14 +4531,7 @@ static inline bool should_try_to_free_swap(struct swap_info_struct *si,
if (mem_cgroup_swap_full(folio) || (vma->vm_flags & VM_LOCKED) ||
folio_test_mlocked(folio))
return true;
- /*
- * If we want to map a page that's in the swapcache writable, we
- * have to detect via the refcount if we're really the exclusive
- * user. Try freeing the swapcache to get rid of the swapcache
- * reference only in case it's likely that we'll be the exclusive user.
- */- return (fault_flags & FAULT_FLAG_WRITE) && !folio_test_ksm(folio) &&
- folio_ref_count(folio) == (extra_refs + folio_nr_pages(folio));
+ return false;
}
static vm_fault_t pte_marker_clear(struct vm_fault *vmf)
@@ -5095,7 +5091,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
* Do it after mapping, so raced page faults will likely see the folio
* in swap cache and wait on the folio lock.
*/
- if (should_try_to_free_swap(si, folio, vma, nr_pages, vmf->flags))
+ if (should_try_to_free_swap(si, folio, vma, vmf->flags))
folio_free_swap(folio);
folio_unlock(folio);But then, one question is whether we'd actually want to try removing the swapcache when we mapped the page writable (iow: exclusive)?
@@ -4512,7 +4516,7 @@ static vm_fault_t remove_device_exclusive_entry(struct vm_fault *vmf)
static inline bool should_try_to_free_swap(struct swap_info_struct *si,
struct folio *folio,
struct vm_area_struct *vma,
- unsigned int extra_refs,
+ bool exclusive,
unsigned int fault_flags)
{
if (!folio_test_swapcache(folio))
@@ -4529,13 +4533,11 @@ static inline bool should_try_to_free_swap(struct swap_info_struct *si,
folio_test_mlocked(folio))
return true;
/*
- * If we want to map a page that's in the swapcache writable, we
- * have to detect via the refcount if we're really the exclusive
- * user. Try freeing the swapcache to get rid of the swapcache
- * reference only in case it's likely that we'll be the exclusive user.
+ * We have an exclusive page that was mapped writable or will soon
+ * be mapped writable (as we are in a write fault). Let's just try
+ * to reclaim swap immediately.
*/- return (fault_flags & FAULT_FLAG_WRITE) && !folio_test_ksm(folio) &&
- folio_ref_count(folio) == (extra_refs + folio_nr_pages(folio));
+ return (fault_flags & FAULT_FLAG_WRITE) && exclusive;
}
static vm_fault_t pte_marker_clear(struct vm_fault *vmf)
@@ -5095,7 +5097,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
* Do it after mapping, so raced page faults will likely see the folio
* in swap cache and wait on the folio lock.
*/
- if (should_try_to_free_swap(si, folio, vma, nr_pages, vmf->flags))
+ if (should_try_to_free_swap(si, folio, vma, exclusive, vmf->flags))
folio_free_swap(folio);
folio_unlock(folio);Signature · 3 lines
Cheers, David
Re: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
Quoted 112 lines · David Hildenbrand (Arm)
Quoted 9 lines
Quoted 4 lines
Quoted 4 lines
That changed that behavior such that we *must* now always fallback to
do_wp_page().What a mess (I didn't ack)
That's awkward, my bad, terribly sorry about this :(
I sincerely apologize for the oversight and the trouble this has caused.
All good, I was just surprised to see a previous optimization partially reverted without a clear reasoning :)
Because it should have removed the handling in
should_try_to_free_swap()as well.It's good that we are discussing it now!
Quoted 4 lines
I haven't seen any performance regression in any workload recently though, or any correctness issue, perhaps the round trip of
do_wp_pagewasn't that bad. It should still catch the reuse folios, just more costly than doing things in-place.Right,
do_wp_page()handles it, after the page was mapped. It adds some overhead, but fortunately no TLB flush if we're just upgrading write permissions.The optimization dates back to pre PageAnonExclusive handling.
Quoted 39 lines
I think we should restore the original check first. We might also want to avoid dropping the swap cache if the folio will not be reused, which was discussed here:
https://lore.kernel.org/linux-mm/CAMgjq7BDfvNXdWH0cqarsujjUn3i3tDDhDkmSg01TR4h-tDorQ@mail.gmail.com/
Maybe extracting some common part into a helper can help make this cleaner.
Quoted 29 lines
So it seems we need a patch before patch 4 to fix the missed swapcache freeing on write faults, because the write bit has already been cleared by the time the folio is mapped and reused?
mm/memory.c +1
-0@@ -4752,6 +4752,7 @@ static void check_swap_exclusive(struct folio*folio, swp_entry_t entry, */ vm_fault_t do_swap_page (struct vm_fault *vmf) { +enum fault_flag fault_flags = vmf->flags;struct vm_area_struct *vma = vmf->vma;struct folio *swapcache =NULL , *folio;struct page *page;mm/memory.c +1
-1@@ -5091,7 +5092,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)* an additional reference to the folio. */ if (should_try_to_free_swap (si, folio, vma, nr_pages + - !folio_test_lru (folio), vmf->flags)) + !folio_test_lru (folio), fault_flags))folio_free_swap (folio);folio_unlock (folio);Quoted 7 lines
Quoted 2 lines
Best Regards
BarryHi Barry,
The problem is more than that, the
exclusive || folio_ref_count(folio) == 1indo_swap_pageis also ineffective now.Exactly.
If the roundtrip through
do_wp_page()is good enough today, we can just domm/memory.c +2
-10@@ -4512,7 +4516,6 @@ static vm_fault_t remove_device_exclusive_entry(struct vm_fault *vmf)static inline bool should_try_to_free_swap (struct swap_info_struct *si,struct folio *folio,struct vm_area_struct *vma, -unsigned int extra_refs,unsigned int fault_flags) {if (!folio_test_swapcache (folio))@@ -4528,14 +4531,7 @@ static inline bool should_try_to_free_swap(struct swap_info_struct *si,if (mem_cgroup_swap_full (folio) || (vma->vm_flags &VM_LOCKED ) ||folio_test_mlocked (folio))return true; -/* -* If we want to map a page that's in the swapcache writable, we -* have to detect via the refcount if we're really the exclusive -* user. Try freeing the swapcache to get rid of the swapcache -* reference only in case it's likely that we'll be the exclusive user. -*/ -return (fault_flags &FAULT_FLAG_WRITE ) && !folio_test_ksm (folio) && -folio_ref_count (folio) == (extra_refs +folio_nr_pages (folio)); +return false; }static vm_fault_t pte_marker_clear (struct vm_fault *vmf)@@ -5095,7 +5091,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)* Do it after mapping, so raced page faults will likely see the folio * in swap cache and wait on the folio lock. */ -if (should_try_to_free_swap (si, folio, vma, nr_pages, vmf->flags)) +if (should_try_to_free_swap (si, folio, vma, vmf->flags))folio_free_swap (folio);folio_unlock (folio);
Quoted 2 lines
But then, one question is whether we'd actually want to try removing the swapcache when we mapped the page writable (iow: exclusive)?
I guess this question comes from my earlier commit c18160dba5ff ("mm: swap: reuse exclusive folio directly instead of wp page faults"), where the folio is reused even for READ faults. In that case, we would miss do_wp_page(), which could later drop the swapcache? Also, again, nobody has reported any regression for this.
Holding swapcache for a clean folio for non-sync swap I/O has the benefit of avoiding a potential pageout(). Now, reuse even for read faults and always dropping swapcache seems to somewhat defeat that benefit. On the other hand, we always drop swapcache for sync I/O to avoid the copy in zRAM or elsewhere consuming memory, so it seems safe enough to always enable reuse in do_swap_page() for sync I/O.
Quoted 16 lines
mm/memory.c +1
-2@@ -4512,7 +4516,7 @@ static vm_fault_t remove_device_exclusive_entry(struct vm_fault *vmf)static inline bool should_try_to_free_swap (struct swap_info_struct *si,struct folio *folio,struct vm_area_struct *vma, -unsigned int extra_refs, +bool exclusive,unsigned int fault_flags) {if (!folio_test_swapcache (folio))@@ -4529,13 +4533,11 @@ static inline bool should_try_to_free_swap(struct swap_info_struct *si, folio_test_mlocked(folio))return true;/* -* If we want to map a page that's in the swapcache writable, we
mm/memory.c static inline bool should_try_to_free_swap(struct swap_info_struct *si, · line 4540
Quoted 10 lines
mm/memory.c +4
-5-* have to detect via the refcount if we're really the exclusive -* user. Try freeing the swapcache to get rid of the swapcache -* reference only in case it's likely that we'll be the exclusive user. +* We have an exclusive page that was mapped writable or will soon +* be mapped writable (as we are in a write fault). Let's just try +* to reclaim swap immediately. */ -return (fault_flags &FAULT_FLAG_WRITE ) && !folio_test_ksm (folio) && -folio_ref_count (folio) == (extra_refs +folio_nr_pages (folio)); +return (fault_flags &FAULT_FLAG_WRITE ) && exclusive;
I assume you mean (fault_flags & FAULT_FLAG_WRITE) || exclusive, or we can just remove (fault_flags & FAULT_FLAG_WRITE) and use "exclusive" instead since we are always using do_wp_page() now, and FAULT_FLAG_WRITE in fault_flags could have been cleared by the reuse of do_swap_page().
Best Regards
Barry
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
Quoted 89 lines · Barry Song
Quoted 78 lines · David Hildenbrand (Arm)
Quoted 6 lines
That's awkward, my bad, terribly sorry about this :(
I sincerely apologize for the oversight and the trouble this has caused.
All good, I was just surprised to see a previous optimization partially reverted without a clear reasoning :)
Because it should have removed the handling in
should_try_to_free_swap()as well.It's good that we are discussing it now!
Quoted 4 lines
I haven't seen any performance regression in any workload recently though, or any correctness issue, perhaps the round trip of
do_wp_pagewasn't that bad. It should still catch the reuse folios, just more costly than doing things in-place.Right,
do_wp_page()handles it, after the page was mapped. It adds some overhead, but fortunately no TLB flush if we're just upgrading write permissions.The optimization dates back to pre PageAnonExclusive handling.
Quoted 7 lines
I think we should restore the original check first. We might also want to avoid dropping the swap cache if the folio will not be reused, which was discussed here:
https://lore.kernel.org/linux-mm/CAMgjq7BDfvNXdWH0cqarsujjUn3i3tDDhDkmSg01TR4h-tDorQ@mail.gmail.com/
Quoted 8 lines
Maybe extracting some common part into a helper can help make this cleaner.
Hi Barry,
The problem is more than that, the
exclusive || folio_ref_count(folio) == 1indo_swap_pageis also ineffective now.Exactly.
If the roundtrip through
do_wp_page()is good enough today, we can just do(quoted diff) +2
-10@@ -4512,7 +4516,6 @@ static vm_fault_t remove_device_exclusive_entry(struct vm_fault *vmf) static inline bool should_try_to_free_swap(struct swap_info_struct *si, struct folio *folio, struct vm_area_struct *vma, - unsigned int extra_refs, unsigned int fault_flags) { if (!folio_test_swapcache(folio))@@ -4528,14 +4531,7 @@ static inline bool should_try_to_free_swap(struct swap_info_struct *si, if (mem_cgroup_swap_full(folio) || (vma->vm_flags & VM_LOCKED) || folio_test_mlocked(folio)) return true; - /* - * If we want to map a page that's in the swapcache writable, we - * have to detect via the refcount if we're really the exclusive - * user. Try freeing the swapcache to get rid of the swapcache - * reference only in case it's likely that we'll be the exclusive user. - */ - return (fault_flags & FAULT_FLAG_WRITE) && !folio_test_ksm(folio) && - folio_ref_count(folio) == (extra_refs + folio_nr_pages(folio)); + return false; } static vm_fault_t pte_marker_clear(struct vm_fault *vmf)@@ -5095,7 +5091,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) * Do it after mapping, so raced page faults will likely see the folio * in swap cache and wait on the folio lock. */ - if (should_try_to_free_swap(si, folio, vma, nr_pages, vmf->flags)) + if (should_try_to_free_swap(si, folio, vma, vmf->flags)) folio_free_swap(folio); folio_unlock(folio);Quoted 2 lines
But then, one question is whether we'd actually want to try removing the swapcache when we mapped the page writable (iow: exclusive)?
I guess this question comes from my earlier commit
c18160dba5ff("mm: swap: reuse exclusive folio directly instead of wp page faults"), where the folio is reused even for READ faults. In that case, we would missdo_wp_page(), which could later drop the swapcache? Also, again, nobody has reported any regression for this.
Quoted 6 lines
Holding swapcache for a clean folio for non-sync swap I/O has the benefit of avoiding a potential
pageout(). Now, reuse even for read faults and always dropping swapcache seems to somewhat defeat that benefit. On the other hand, we always drop swapcache for sync I/O to avoid the copy in zRAM or elsewhere consuming memory, so it seems safe enough to always enable reuse indo_swap_page()for sync I/O.
Right, I meant during write faults, when a write is expected. See below.
Quoted 21 lines
Quoted 21 lines
(quoted diff) +2
-5@@ -4512,7 +4516,7 @@ static vm_fault_t remove_device_exclusive_entry(struct vm_fault *vmf) static inline bool should_try_to_free_swap(struct swap_info_struct *si, struct folio *folio, struct vm_area_struct *vma, - unsigned int extra_refs, + bool exclusive, unsigned int fault_flags) { if (!folio_test_swapcache(folio))@@ -4529,13 +4533,11 @@ static inline bool should_try_to_free_swap(struct swap_info_struct *si, folio_test_mlocked(folio)) return true; /* - * If we want to map a page that's in the swapcache writable, we - * have to detect via the refcount if we're really the exclusive - * user. Try freeing the swapcache to get rid of the swapcache - * reference only in case it's likely that we'll be the exclusive user. + * We have an exclusive page that was mapped writable or will soon
Quoted 8 lines
Review static inline bool should_try_to_free_swap(struct swap_info_struct *si, · line 4540
Quoted 6 lines
(quoted diff) +3
-2+ * be mapped writable (as we are in a write fault). Let's just try + * to reclaim swap immediately. */ - return (fault_flags & FAULT_FLAG_WRITE) && !folio_test_ksm(folio) && - folio_ref_count(folio) == (extra_refs + folio_nr_pages(folio)); + return (fault_flags & FAULT_FLAG_WRITE) && exclusive;I assume you mean (
fault_flags&FAULT_FLAG_WRITE) || exclusive, or
No, I meant "we are serving a write fault (write is definitely going to happen) and we are definitely reusing the page (exclusive).
Quoted 2 lines
we can just remove (
fault_flags&FAULT_FLAG_WRITE) and use "exclusive" instead since we are always usingdo_wp_page()now,
If you drop the "FAULT_FLAG_WRITE", you'd remove clean pages (that will likely stay clean as no write fault) from the swapcache, As you correctly say above, can avoid a pageout(), so I think we should keep that.
Quoted 2 lines
and
FAULT_FLAG_WRITEinfault_flagscould have been cleared by the reuse ofdo_swap_page().
Ah, yes, that existing handling is nasty. We should look into not messing with fault flags. Something like the following
mm/memory.c +2 -4
@@ -5052,10 +5052,8 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)if ((vma->vm_flags &VM_WRITE ) && !userfaultfd_pte_wp (vma, pte) && !pte_needs_soft_dirty_wp (vma, pte)) { pte =pte_mkwrite (pte, vma); -if (vmf->flags &FAULT_FLAG_WRITE ) { +if (vmf->flags &FAULT_FLAG_WRITE ) pte =pte_mkdirty (pte); - vmf->flags &= ~FAULT_FLAG_WRITE ; - } } rmap_flags |=RMAP_EXCLUSIVE ; }
@@ -5112,7 +5110,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)folio_put (swapcache); } -if (vmf->flags &FAULT_FLAG_WRITE ) { +if ((vmf->flags &FAULT_FLAG_WRITE ) && !pte_write (pte)) { ret |=do_wp_page (vmf);if (ret &VM_FAULT_ERROR ) ret &=VM_FAULT_ERROR ;
Signature · 3 lines
Cheers, David
Re: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
On Tue, Jun 30, 2026 at 1:48 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]
mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5116
Quoted 7 lines
mm/memory.c +1
-1-if (vmf->flags &FAULT_FLAG_WRITE ) { +if ((vmf->flags &FAULT_FLAG_WRITE ) && !pte_write (pte)) { ret |=do_wp_page (vmf);if (ret &VM_FAULT_ERROR ) ret &=VM_FAULT_ERROR ;
Thanks! I'm working on a v3, but one thing still seems a bit odd to me.
In patch 1, we're removing the local LRU drain from do_wp_page(). But in patch 4, do_swap_page() calls do_wp_page() for folios without the "exclusive" hint, since folios with the hint have already been reused in do_swap_page().
This can happen when we allocate a new folio whose swap entry lacks the exclusive flag, even though the folio is still reusable. For example, in the scenario I mentioned in patch 1, the swap count is effectively 1, but there is no exclusive flag:
#include <sys/mman.h>
int main(int argc, char *argv[])
{
int i;
#define SIZE 30*1024*1024
while(1) {
volatile int *p = mmap(0, SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
volatile int q;
for (int i = 0; i < SIZE/sizeof(int); i++)
p[i] = i;
madvise((void *)p, SIZE, MADV_PAGEOUT);
if (!fork())
_exit(0);
for (int i = 0; i < SIZE/sizeof(int); i++)
q = p[i];
for (int i = 0; i < SIZE/sizeof(int); i++)
p[i] = i;
munmap(p, SIZE);
}
return 0;
}The newly allocated folio will likely still be sitting in this CPU's local LRU cache. If we drop the drain in patch 1, it seems there is little point in calling do_wp_page() from do_swap_page(), since we won't be able to reuse the folio while it remains in the local LRU cache.
So I'm wondering whether we should keep the drain in patch 1 by checking for a refcount of 3, or instead do something like the following in patch 4:
if ((vmf->flags & FAULT_FLAG_WRITE) && !pte_write(pte)) {
lru_add_drain();
ret |= do_wp_page(vmf);
if (ret & VM_FAULT_ERROR)
ret &= VM_FAULT_ERROR;
}Otherwise, the combination of patches 1 and 4 seems somewhat inconsistent. It feels like a strange mix of sweet, spicy, hot, and cold—kind of contradictory :-)
Best Regards
Barry
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
Quoted 50 lines · Barry Song
On Tue, Jun 30, 2026 at 1:48 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5116
Quoted 7 lines
mm/memory.c +1
-1-if (vmf->flags &FAULT_FLAG_WRITE ) { +if ((vmf->flags &FAULT_FLAG_WRITE ) && !pte_write (pte)) { ret |=do_wp_page (vmf);if (ret &VM_FAULT_ERROR ) ret &=VM_FAULT_ERROR ;Thanks! I'm working on a v3, but one thing still seems a bit odd to me.
In patch 1, we're removing the local LRU drain from
do_wp_page(). But in patch 4,do_swap_page()callsdo_wp_page()for folios without the "exclusive" hint, since folios with the hint have already been reused indo_swap_page().This can happen when we allocate a new folio whose swap entry lacks the exclusive flag, even though the folio is still reusable. For example, in the scenario I mentioned in patch 1, the swap count is effectively 1, but there is no exclusive flag:
#include <
sys/mman.h>int
main(int argc, char *argv[]){ int i; #define SIZE 30*1024*1024 while(1) { volatile int *p = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); volatile int q; for (int i = 0; i < SIZE/sizeof(int); i++) p[i] = i; madvise((void *)p, SIZE, MADV_PAGEOUT); if (!fork()) _exit(0); for (int i = 0; i < SIZE/sizeof(int); i++) q = p[i]; for (int i = 0; i < SIZE/sizeof(int); i++) p[i] = i; munmap(p, SIZE); } return 0; }
Quoted 5 lines
The newly allocated folio will likely still be sitting in this CPU's local LRU cache. If we drop the drain in patch 1, it seems there is little point in calling
do_wp_page()fromdo_swap_page(), since we won't be able to reuse the folio while it remains in the local LRU cache.
Well, we call do_wp_page() here to avoid another page fault where we would end up in do_wp_page() and actually break COW.
Quoted 13 lines
So I'm wondering whether we should keep the drain in patch 1 by checking for a refcount of 3, or instead do something like the following in patch 4:
if ((vmf->flags & FAULT_FLAG_WRITE) && !pte_write(pte)) { lru_add_drain(); ret |= do_wp_page(vmf); if (ret & VM_FAULT_ERROR) ret &= VM_FAULT_ERROR; }
Quoted 3 lines
Otherwise, the combination of patches 1 and 4 seems somewhat inconsistent. It feels like a strange mix of sweet, spicy, hot, and cold—kind of contradictory :-)
Yes, this needs a proper thought.
What you describe is relevant for
e.g.,
swapout
fork -> exclusive bit cleared
child exit -> parent is single user
swapin -> added to swapcache and LRU cache
This is not very odd, so we should keep that working (I think I have micro-benchmarks for that).
Signature · 3 lines
Cheers, David
Re: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
On Wed, Jul 1, 2026 at 4:18 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]
Quoted 9 lines
Quoted 6 lines
The newly allocated folio will likely still be sitting in this CPU's local LRU cache. If we drop the drain in patch 1, it seems there is little point in calling
do_wp_page()fromdo_swap_page(), since we won't be able to reuse the folio while it remains in the local LRU cache.Well, we call
do_wp_page()here to avoid another page fault where we would end up indo_wp_page()and actually break COW.
Yep. We still need do_wp_page() to handle CoW, but we could actually reuse the folio if we don't drop the LRU drain.
Quoted 24 lines
Quoted 14 lines
So I'm wondering whether we should keep the drain in patch 1 by checking for a refcount of 3, or instead do something like the following in patch 4:
if ((vmf->flags & FAULT_FLAG_WRITE) && !pte_write(pte)) { lru_add_drain(); ret |= do_wp_page(vmf); if (ret & VM_FAULT_ERROR) ret &= VM_FAULT_ERROR; }Quoted 3 lines
Otherwise, the combination of patches 1 and 4 seems somewhat inconsistent. It feels like a strange mix of sweet, spicy, hot, and cold—kind of contradictory :-)
Yes, this needs a proper thought.
What you describe is relevant for
e.g.,
Quoted 8 lines
swapout
fork -> exclusive bit cleared
child exit -> parent is single user
swapin -> added to swapcache and LRU cacheThis is not very odd, so we should keep that working (I think I have micro-benchmarks for that).
Maybe the simplest approach is to keep the drain in patch 1 and just make the code improvements you suggested in [1]?
That way, we won't regress the potential reuse indo_swap_page().
[1] https://lore.kernel.org/all/[email protected]/
Thanks
Barry
David Hildenbrand (Arm)
maintainerRe: [PATCH v2 4/4] mm: try to free swapcache for non-LRU folios
Quoted 45 lines · Barry Song
On Wed, Jul 1, 2026 at 4:18 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]Quoted 9 lines
Quoted 6 lines
The newly allocated folio will likely still be sitting in this CPU's local LRU cache. If we drop the drain in patch 1, it seems there is little point in calling
do_wp_page()fromdo_swap_page(), since we won't be able to reuse the folio while it remains in the local LRU cache.Well, we call
do_wp_page()here to avoid another page fault where we would end up indo_wp_page()and actually break COW.Yep. We still need
do_wp_page()to handle CoW, but we could actually reuse the folio if we don't drop the LRU drain.Quoted 30 lines
Quoted 14 lines
So I'm wondering whether we should keep the drain in patch 1 by checking for a refcount of 3, or instead do something like the following in patch 4:
if ((vmf->flags & FAULT_FLAG_WRITE) && !pte_write(pte)) { lru_add_drain(); ret |= do_wp_page(vmf); if (ret & VM_FAULT_ERROR) ret &= VM_FAULT_ERROR; }Quoted 3 lines
Otherwise, the combination of patches 1 and 4 seems somewhat inconsistent. It feels like a strange mix of sweet, spicy, hot, and cold—kind of contradictory :-)
Yes, this needs a proper thought.
What you describe is relevant for
e.g.,
swapout
fork -> exclusive bit cleared
child exit -> parent is single user
swapin -> added to swapcache and LRU cache
Quoted 5 lines
Quoted 2 lines
This is not very odd, so we should keep that working (I think I have micro-benchmarks for that).
Maybe the simplest approach is to keep the drain in patch 1 and just make the code improvements you suggested in [1]?
Yeah, probably best for that scenario.
Signature · 3 lines
Cheers, David