mm: Avoiding split large folios if swap has no space
Subsystems (6)
Barry Song (Xiaomi)
reviewerRFC PATCH 1/1 mm: Avoiding split large folios if swap has no space
When swap is disabled or exhausted, swap slot allocation may fail during swapout, causing large folios to be split into small folios. The splitting is reasonable when we truly fail to obtain contiguous swap slots, but it is pointless in the no-space case.
A simple way to reproduce this is to invoke MADV_PAGEOUT on a system with mTHP enabled but without swap configured.
#define SIZE (16 * 1024 * 1024)
int main(void)
{
char *buf = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
memset(buf, 1, SIZE);
madvise(buf, SIZE, MADV_PAGEOUT);
munmap(buf, SIZE);
return 0;}
With 16KB mTHP enabled, we observe:
~ # cat /sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split 1024
This patch checks swap space before splitting. If there is no available space, it skips splitting. After the patch, we observe:
~ # cat /sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split 0
--- mm/vmscan.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)
mm/vmscan.c +13 -2
@@ -339,8 +339,7 @@ static bool can_demote(int nid, struct scan_control *sc,return !nodes_empty (allowed_mask); } -static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, -int nid, +static inline bool __can_reclaim_anon_pages (struct mem_cgroup *memcg,struct scan_control *sc) {if (memcg ==NULL ) {
@@ -356,6 +355,16 @@ static inline bool can_reclaim_anon_pages(struct mem_cgroup *memcg,return true; } +return false; +} + +static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, +int nid, +struct scan_control *sc) +{ +if (__can_reclaim_anon_pages (memcg, sc)) +return true; +/* * The page can not be swapped. *
@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,if (!folio_test_large (folio)) goto activate_locked_split; +if (!__can_reclaim_anon_pages (memcg, sc)) + goto activate_locked_split;/* Fallback to swap normal pages */ if (split_folio_to_list (folio, folio_list)) goto activate_locked;
Signature · 1 line
2.39.3 (Apple Git-146)
Quoted 6 lines · Barry Song (Xiaomi)
When swap is disabled or exhausted, swap slot allocation may fail during swapout, causing large folios to be split into small folios. The splitting is reasonable when we truly fail to obtain contiguous swap slots, but it is pointless in the no-space case.
Hi Barry,
Are we sure splitting is pointless? Since b1f202060afe ("mm: remap unused subpages to shared zeropage when splitting isolated thp") a split can lead to memory reclaim.
I've been far removed from the reclaim code for quite some time but im assuming in this case we can actually reclaim some unused memory which might be beneficial if there is memory pressure. However there are some discussions on whether we should further guard this zeropage remapping behavior [1].
[1] - https://lore.kernel.org/lkml/[email protected]/
Cheers,
-- Nico
Quoted 89 lines
A simple way to reproduce this is to invoke
MADV_PAGEOUTon a system with mTHP enabled but without swap configured.#define SIZE (16 * 1024 * 1024)
int main(void){ char *buf = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); memset(buf, 1, SIZE); madvise(buf, SIZE, MADV_PAGEOUT); munmap(buf, SIZE); return 0;}
With 16KB mTHP enabled, we observe:
~ # cat/sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split1024This patch checks swap space before splitting. If there is no available space, it skips splitting. After the patch, we observe:
~ # cat/sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split0Reported-by Nanzhe Zhao <[email protected]>Cc David Hildenbrand <[email protected]>Cc Lorenzo Stoakes <[email protected]>Cc Zi Yan <[email protected]>Cc Baolin Wang <[email protected]>Cc Liam R. Howlett <[email protected]>Cc Nico Pache <[email protected]>Cc Ryan Roberts <[email protected]>Cc Dev Jain <[email protected]>Cc Lance Yang <[email protected]>Cc Kairui Song <[email protected]>Cc Qi Zheng <[email protected]>Cc Shakeel Butt <[email protected]>Cc Axel Rasmussen <[email protected]>Cc Yuanchu Xie <[email protected]>Cc Wei Xu <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/vmscan.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)mm/vmscan.c +13
-3@@ -339,8 +339,7 @@ static bool can_demote(int nid, struct scan_control *sc,return !nodes_empty (allowed_mask); } -static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, -int nid, +static inline bool __can_reclaim_anon_pages (struct mem_cgroup *memcg,struct scan_control *sc) {if (memcg ==NULL ) {@@ -356,6 +355,16 @@ static inline bool can_reclaim_anon_pages(struct mem_cgroup *memcg,return true; } +return false; +} + +static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, +int nid, +struct scan_control *sc) +{ +if (__can_reclaim_anon_pages (memcg, sc)) +return true; +/* * The page can not be swapped. * @@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,if (!folio_test_large (folio)) goto activate_locked_split; +if (!__can_reclaim_anon_pages (memcg, sc)) + goto activate_locked_split;/* Fallback to swap normal pages */ if (split_folio_to_list (folio, folio_list)) goto activate_locked; --2.39.3 (Apple Git-146)
Barry Song
reviewerQuoted 15 lines · Nico Pache
Quoted 6 lines · Barry Song (Xiaomi)
When swap is disabled or exhausted, swap slot allocation may fail during swapout, causing large folios to be split into small folios. The splitting is reasonable when we truly fail to obtain contiguous swap slots, but it is pointless in the no-space case.
Hi Barry,
Are we sure splitting is pointless? Since
b1f202060afe("mm: remap unused subpages to shared zeropage when splitting isolated thp") a split can lead to memory reclaim.
Quoted 5 lines
I've been far removed from the reclaim code for quite some time but im assuming in this case we can actually reclaim some unused memory which might be beneficial if there is memory pressure. However there are some discussions on whether we should further guard this zeropage remapping behavior [1].
Hi Nico,
nice to hear your feedback.
We already have code to split partially unmapped subpages to reclaim memory before folio_alloc_swap(folio):
/*
* Split partially mapped folios right away.
* We can free the unmapped pages without IO. */
if
(data_race(!list_empty(&folio->_deferred_list) &&
folio_test_partially_mapped(folio)) &&
split_folio_to_list(folio, folio_list))
goto activate_locked;But it seems you are suggesting that we could split even fully mapped folios, possibly to look for zero-filled subpages within them?
I feel this only makes sense when there is an actual split requirement. In cases where we are running out of swap or swap is unavailable, forcing a split seems incorrect. Even in a normal mTHP case without swapping involved, an mTHP can still contain zero-filled subpages.
Recently, we did a number of experiments to relocate zero-filled pages to zero_pfn by forcing SWAPOUT, and then mapping them back to zero_pfn during swapin via my previous RFC - mm: map zero-filled pages to zero_pfn while doing swap-in[1]. However, we found no improvement in reducing memory usage, as these zero-filled pages are quite volatile and are quickly overwritten with non-zero data. So I gave up efforts to detect zero-filled pages and perform zero_pfn remapping.
In conclusion, I feel it is not useful to detect zero-filled subpages, since they are quickly replaced by non-zero data. In our experiments, we triggered forced SWAPOUT every minute after detecting zero-filled pages, but found no reduction in memory usage.
[1] https://lore.kernel.org/linux-mm/[email protected]/
Quoted 2 lines
Best Regards
Barry
David Hildenbrand (Arm)
maintainerQuoted 8 lines · Barry Song (Xiaomi)
When swap is disabled or exhausted, swap slot allocation may fail during swapout, causing large folios to be split into small folios. The splitting is reasonable when we truly fail to obtain contiguous swap slots, but it is pointless in the no-space case.
A simple way to reproduce this is to invoke
MADV_PAGEOUTon a system with mTHP enabled but without swap configured.
Would we ever run into that code without MADV_PAGEOUT, or is that a MADV_PAGEOUT-special thing?
I recall that can_reclaim_anon_pages() would make vmscan skip such code, but it's tricky.
Signature · 3 lines
Cheers, David
Barry Song
reviewerQuoted 8 lines · David Hildenbrand (Arm)
Quoted 6 lines · Barry Song (Xiaomi)
When swap is disabled or exhausted, swap slot allocation may fail during swapout, causing large folios to be split into small folios. The splitting is reasonable when we truly fail to obtain contiguous swap slots, but it is pointless in the no-space case.
Quoted 8 lines
Quoted 2 lines
A simple way to reproduce this is to invoke
MADV_PAGEOUTon a system with mTHP enabled but without swap configured.Would we ever run into that code without
MADV_PAGEOUT, or is that a MADV_PAGEOUT-special thing?I recall that
can_reclaim_anon_pages()would make vmscan skip such code, but it's tricky.
I guess there could also be cases such as get_nr_swap_pages() == 1 while folio_nr_pages() > 1, for example, where can_reclaim_anon_pages() cannot help.
Best Regards
Barry
David Hildenbrand (Arm)
maintainerQuoted 83 lines · Barry Song (Xiaomi)
When swap is disabled or exhausted, swap slot allocation may fail during swapout, causing large folios to be split into small folios. The splitting is reasonable when we truly fail to obtain contiguous swap slots, but it is pointless in the no-space case.
A simple way to reproduce this is to invoke
MADV_PAGEOUTon a system with mTHP enabled but without swap configured.#define SIZE (16 * 1024 * 1024)
int main(void){ char *buf = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); memset(buf, 1, SIZE); madvise(buf, SIZE, MADV_PAGEOUT); munmap(buf, SIZE); return 0;}
With 16KB mTHP enabled, we observe:
~ # cat/sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split1024This patch checks swap space before splitting. If there is no available space, it skips splitting. After the patch, we observe:
~ # cat/sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split0Reported-by Nanzhe Zhao <[email protected]>Cc David Hildenbrand <[email protected]>Cc Lorenzo Stoakes <[email protected]>Cc Zi Yan <[email protected]>Cc Baolin Wang <[email protected]>Cc Liam R. Howlett <[email protected]>Cc Nico Pache <[email protected]>Cc Ryan Roberts <[email protected]>Cc Dev Jain <[email protected]>Cc Lance Yang <[email protected]>Cc Kairui Song <[email protected]>Cc Qi Zheng <[email protected]>Cc Shakeel Butt <[email protected]>Cc Axel Rasmussen <[email protected]>Cc Yuanchu Xie <[email protected]>Cc Wei Xu <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/vmscan.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)mm/vmscan.c +11
-2@@ -339,8 +339,7 @@ static bool can_demote(int nid, struct scan_control *sc,return !nodes_empty (allowed_mask); } -static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, -int nid, +static inline bool __can_reclaim_anon_pages (struct mem_cgroup *memcg,struct scan_control *sc) {if (memcg ==NULL ) {@@ -356,6 +355,16 @@ static inline bool can_reclaim_anon_pages(struct mem_cgroup *memcg,return true; } +return false; +} + +static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, +int nid, +struct scan_control *sc) +{ +if (__can_reclaim_anon_pages (memcg, sc)) +return true; +/* * The page can not be swapped. *
mm/vmscan.c static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1293
Quoted 5 lines
mm/vmscan.c +2
-0@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,if (!folio_test_large (folio)) goto activate_locked_split; +if (!__can_reclaim_anon_pages (memcg, sc)) + goto activate_locked_split;
Why are we even trying to allocate swap space if we cannot reclaim such pages? Makes we wonder whether we would want to have that check earlier, before the folio_alloc_swap().
Any downsides?
Signature · 3 lines
Cheers, David
Barry Song (Xiaomi)
reviewerOn Fri, Jun 19, 2026 at 10:04 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]
Quoted 14 lines
Review static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1293
Quoted 9 lines
/* * The page can not be swapped. *(quoted diff) +2
-0@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, if (!folio_test_large(folio)) goto activate_locked_split; + if (!__can_reclaim_anon_pages(memcg, sc)) + goto activate_locked_split;Why are we even trying to allocate swap space if we cannot reclaim such pages? Makes we wonder whether we would want to have that check earlier, before the
folio_alloc_swap().
Quoted 1 line
Any downsides?
I don't think there are any obvious downsides there. One issue is that the memcg may not be passed from reclaim_pages(), so memcg would always be NULL. However, the folio could still belong to a memcg whose swap quota has been exhausted. In that case, my __can_reclaim_anon_pages() will fail when checking whether we can swap out. But switching to folio_memcg() also seems awkward.
So I feel Kairui’s suggestion [1] might be the best approach. In folio_alloc_swap(), we return -EAGAIN to tell vmscan.c that we can split the folio and retry the swap-out.
only when there are sufficient swap slots and sufficient memcg swap quota do we return -EAGAIN, allowing vmscan to perform a split.
mm/swapfile.c +7 -2
@@ -1755,6 +1755,9 @@ int folio_alloc_swap(struct folio *folio)VM_WARN_ON_ONCE (1 );return -EINVAL ; } + +if (get_nr_swap_pages () < (1 << order)) +return -ENOMEM ; } again:
@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ; + }if (unlikely (!folio_test_swapcache (folio))) -return -ENOMEM ; +return -EAGAIN ;return 0 ; }
mm/vmscan.c +4 -2
@@ -1257,6 +1257,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,*/ if (folio_test_anon (folio) &&folio_test_swapbacked (folio) && !folio_test_swapcache (folio)) { +int ret; +if (!(sc->gfp_mask & __GFP_IO)) goto keep_locked;if (folio_maybe_dma_pinned (folio))
@@ -1275,10 +1277,10 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, split_folio_to_list(folio, folio_list)) goto activate_locked; } -if (folio_alloc_swap (folio)) { +if ((ret =folio_alloc_swap (folio))) {int __maybe_unused order =folio_order (folio); -if (!folio_test_large (folio)) +if (!folio_test_large (folio) || ret != -EAGAIN ) goto activate_locked_split;/* Fallback to swap normal pages */ if (split_folio_to_list (folio, folio_list))
What’s your view on this, David?
[1] https://lore.kernel.org/linux-mm/CAMgjq7Bmi2XYxPc4tVO8KTWSuj8jpt-c-JueqYRK1kLC_nmBqA@mail.gmail.com/
Best Regards
Barry
Baolin Wang
reviewerQuoted 36 lines · Barry Song (Xiaomi)
On Fri, Jun 19, 2026 at 10:04 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]Quoted 14 lines
Review static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1293
Quoted 9 lines
/* * The page can not be swapped. *(quoted diff) +2
-0@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, if (!folio_test_large(folio)) goto activate_locked_split; + if (!__can_reclaim_anon_pages(memcg, sc)) + goto activate_locked_split;Why are we even trying to allocate swap space if we cannot reclaim such pages? Makes we wonder whether we would want to have that check earlier, before the
folio_alloc_swap().Quoted 1 line
Any downsides?
I don't think there are any obvious downsides there. One issue is that the memcg may not be passed from
reclaim_pages(), so memcg would always be NULL. However, the folio could still belong to a memcg whose swap quota has been exhausted. In that case, my__can_reclaim_anon_pages()will fail when checking whether we can swap out. But switching tofolio_memcg()also seems awkward.So I feel Kairui’s suggestion [1] might be the best approach. In
folio_alloc_swap(), we return-EAGAINto tellvmscan.cthat we can split the folio and retry the swap-out.
only when there are sufficient swap slots and sufficient memcg swap quota do we return-EAGAIN, allowing vmscan to perform a split.mm/swapfile.c +0
-0
mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1760
Quoted 6 lines
mm/swapfile.c +3
-0@@ -1755,6 +1755,9 @@ int folio_alloc_swap(struct folio *folio)VM_WARN_ON_ONCE (1 );return -EINVAL ; } + +if (get_nr_swap_pages () < (1 << order)) +return -ENOMEM ;
Shouldn't this return -EAGAIN? Suppose we try to swap out an order-9 large folio but get_nr_swap_pages() returns 256, then we'd still need to split the order-9 large folio to reclaim some memory.
Quoted 33 lines
mm/swapfile.c +4
-2} again:@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ; + }if (unlikely (!folio_test_swapcache (folio))) -return -ENOMEM ; +return -EAGAIN ;return 0 ; }mm/vmscan.c +2
-0@@ -1257,6 +1257,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,*/ if (folio_test_anon (folio) &&folio_test_swapbacked (folio) && !folio_test_swapcache (folio)) { +int ret; +if (!(sc->gfp_mask & __GFP_IO)) goto keep_locked;if (folio_maybe_dma_pinned (folio))
mm/vmscan.c static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1280
Quoted 5 lines
mm/vmscan.c +1
-1@@ -1275,10 +1277,10 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, split_folio_to_list(folio, folio_list)) goto activate_locked; } -if (folio_alloc_swap (folio)) { +if ((ret =folio_alloc_swap (folio))) {
Also, please give shmem some love (shmem also calls folio_alloc_swap() when swapping out) :)
Barry Song
reviewerQuoted 35 lines · Baolin Wang
Quoted 31 lines · Barry Song (Xiaomi)
On Fri, Jun 19, 2026 at 10:04 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]Quoted 14 lines
Review static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1293
Quoted 9 lines
/* * The page can not be swapped. *(quoted diff) +2
-0@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, if (!folio_test_large(folio)) goto activate_locked_split; + if (!__can_reclaim_anon_pages(memcg, sc)) + goto activate_locked_split;Why are we even trying to allocate swap space if we cannot reclaim such pages? Makes we wonder whether we would want to have that check earlier, before the
folio_alloc_swap().Quoted 1 line
Any downsides?
I don't think there are any obvious downsides there. One issue is that the memcg may not be passed from
reclaim_pages(), so memcg would always be NULL. However, the folio could still belong to a memcg whose swap quota has been exhausted. In that case, my__can_reclaim_anon_pages()will fail when checking whether we can swap out. But switching tofolio_memcg()also seems awkward.So I feel Kairui’s suggestion [1] might be the best approach. In
folio_alloc_swap(), we return-EAGAINto tellvmscan.cthat we can split the folio and retry the swap-out.
only when there are sufficient swap slots and sufficient memcg swap quota do we return-EAGAIN, allowing vmscan to perform a split.
Quoted 15 lines
mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1760
Quoted 11 lines
mm/swapfile.c +3
-0@@ -1755,6 +1755,9 @@ int folio_alloc_swap(struct folio *folio)VM_WARN_ON_ONCE (1 );return -EINVAL ; } + +if (get_nr_swap_pages () < (1 << order)) +return -ENOMEM ;Shouldn't this return
-EAGAIN? Suppose we try to swap out an order-9 large folio butget_nr_swap_pages()returns 256, then we'd still need to split the order-9 large folio to reclaim some memory.
I guess Kairui has the opposite view. Quoting Kairui:
"- 1. the mem_cgroup_try_charge_swap in it failed
- 2. allocation failed but nr_swap_pages > folio size
- 3. allocation failed because all devices are full or unusable (roughly nr_swap_pages < folio size)
Only case 2 requires splitting. __can_reclaim_anon_pages also checks demote which is not related to swap."
For example, if get_nr_swap_pages() returns 1, we can only swap out a single page after splitting. Is that really worth it? On the other hand, if it returns 256, we could at least swap out half of an order-9 large folio. Isn't that worthwhile?
Quoted 34 lines
Quoted 34 lines
mm/swapfile.c +4
-2} again:@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ; + }if (unlikely (!folio_test_swapcache (folio))) -return -ENOMEM ; +return -EAGAIN ;return 0 ; }mm/vmscan.c +2
-0@@ -1257,6 +1257,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,*/ if (folio_test_anon (folio) &&folio_test_swapbacked (folio) && !folio_test_swapcache (folio)) { +int ret; +if (!(sc->gfp_mask & __GFP_IO)) goto keep_locked;if (folio_maybe_dma_pinned (folio))@@ -1275,10 +1277,10 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,
Quoted 8 lines
mm/vmscan.c static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1280
Quoted 5 lines
mm/vmscan.c +1
-1split_folio_to_list(folio, folio_list)) goto activate_locked; } -if (folio_alloc_swap (folio)) { +if ((ret =folio_alloc_swap (folio))) {Also, please give shmem some love (shmem also calls
folio_alloc_swap()when swapping out) :)
Sure. I'm pretty sure I love shmem. :-)
Best Regards
Barry
Baolin Wang
reviewerQuoted 60 lines · Barry Song
Quoted 35 lines · Baolin Wang
Quoted 31 lines · Barry Song (Xiaomi)
On Fri, Jun 19, 2026 at 10:04 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]Quoted 14 lines
Review static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1293
Quoted 9 lines
/* * The page can not be swapped. *(quoted diff) +2
-0@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, if (!folio_test_large(folio)) goto activate_locked_split; + if (!__can_reclaim_anon_pages(memcg, sc)) + goto activate_locked_split;Why are we even trying to allocate swap space if we cannot reclaim such pages? Makes we wonder whether we would want to have that check earlier, before the
folio_alloc_swap().Quoted 1 line
Any downsides?
I don't think there are any obvious downsides there. One issue is that the memcg may not be passed from
reclaim_pages(), so memcg would always be NULL. However, the folio could still belong to a memcg whose swap quota has been exhausted. In that case, my__can_reclaim_anon_pages()will fail when checking whether we can swap out. But switching tofolio_memcg()also seems awkward.So I feel Kairui’s suggestion [1] might be the best approach. In
folio_alloc_swap(), we return-EAGAINto tellvmscan.cthat we can split the folio and retry the swap-out.
only when there are sufficient swap slots and sufficient memcg swap quota do we return-EAGAIN, allowing vmscan to perform a split.Quoted 15 lines
mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1760
Quoted 11 lines
mm/swapfile.c +3
-0@@ -1755,6 +1755,9 @@ int folio_alloc_swap(struct folio *folio)VM_WARN_ON_ONCE (1 );return -EINVAL ; } + +if (get_nr_swap_pages () < (1 << order)) +return -ENOMEM ;Shouldn't this return
-EAGAIN? Suppose we try to swap out an order-9 large folio butget_nr_swap_pages()returns 256, then we'd still need to split the order-9 large folio to reclaim some memory.I guess Kairui has the opposite view. Quoting Kairui:
"- 1. the
mem_cgroup_try_charge_swapin it failed
- 2. allocation failed butnr_swap_pages> folio size
- 3. allocation failed because all devices are full or unusable (roughlynr_swap_pages< folio size)
Quoted 7 lines
Only case 2 requires splitting.
__can_reclaim_anon_pagesalso checks demote which is not related to swap."For example, if
get_nr_swap_pages()returns 1, we can only swap out a single page after splitting. Is that really worth it? On the other hand, if it returns 256, we could at least swap out half of an order-9 large folio. Isn't that worthwhile?
The former doesn't seem necessary, but the latter might be worthwhile.
After splitting, we could potentially avoid premature OOM. Of course, splitting also means losing an order-9 large folio, but we can rely on khugepaged to collapse it into a THP again. But the consequences of OOM are arguably more severe?
Anyway, just raising my concern here (I don't have data at this point to justify whether more complex logic is needed).
Quoted 40 lines
Quoted 31 lines
Quoted 31 lines
mm/swapfile.c +4
-2again:@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ; + }if (unlikely (!folio_test_swapcache (folio))) -return -ENOMEM ; +return -EAGAIN ;return 0 ; }mm/vmscan.c +2
-0@@ -1257,6 +1257,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,*/ if (folio_test_anon (folio) &&folio_test_swapbacked (folio) && !folio_test_swapcache (folio)) { +int ret; +if (!(sc->gfp_mask & __GFP_IO)) goto keep_locked;if (folio_maybe_dma_pinned (folio))@@ -1275,10 +1277,10 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,Quoted 8 lines
mm/vmscan.c static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1280
Quoted 5 lines
mm/vmscan.c +1
-1split_folio_to_list(folio, folio_list)) goto activate_locked; } -if (folio_alloc_swap (folio)) { +if ((ret =folio_alloc_swap (folio))) {Also, please give shmem some love (shmem also calls
folio_alloc_swap()when swapping out) :)
Quoted 1 line
Sure. I'm pretty sure I love shmem. :-)
Thanks.
David Hildenbrand (Arm)
maintainerQuoted 36 lines · Barry Song (Xiaomi)
On Fri, Jun 19, 2026 at 10:04 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]Quoted 14 lines
Review static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1293
Quoted 9 lines
/* * The page can not be swapped. *(quoted diff) +2
-0@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, if (!folio_test_large(folio)) goto activate_locked_split; + if (!__can_reclaim_anon_pages(memcg, sc)) + goto activate_locked_split;Why are we even trying to allocate swap space if we cannot reclaim such pages? Makes we wonder whether we would want to have that check earlier, before the
folio_alloc_swap().Quoted 1 line
Any downsides?
I don't think there are any obvious downsides there. One issue is that the memcg may not be passed from
reclaim_pages(), so memcg would always be NULL. However, the folio could still belong to a memcg whose swap quota has been exhausted. In that case, my__can_reclaim_anon_pages()will fail when checking whether we can swap out. But switching tofolio_memcg()also seems awkward.So I feel Kairui’s suggestion [1] might be the best approach. In
folio_alloc_swap(), we return-EAGAINto tellvmscan.cthat we can split the folio and retry the swap-out.
only when there are sufficient swap slots and sufficient memcg swap quota do we return-EAGAIN, allowing vmscan to perform a split.mm/swapfile.c +0
-0
mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1760
Quoted 6 lines
mm/swapfile.c +3
-0@@ -1755,6 +1755,9 @@ int folio_alloc_swap(struct folio *folio)VM_WARN_ON_ONCE (1 );return -EINVAL ; } + +if (get_nr_swap_pages () < (1 << order)) +return -ENOMEM ;
I guess we could be clearer with the return value:
-> !get_nr_swap_pages() -> -ENOSPC / -ENOMEM
(no space at all)
-> get_nr_swap_pages() < (1 << order) -> -E2BIG
(there is some space, but not for the full thing)
But now I wonder whether we would also want to check "is there any free swap space", not just "is there any swap".
Essentially, try returning -E2BIG if there is the chance to swap out after split, and -ENOSPC / -ENOMEM if a split wouldn't help.
mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1777
Quoted 11 lines
mm/swapfile.c +2
-1} again:@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ;
Here we wouldn't have the information whether we could charge after a split.
So that would require a rework to signal this more cleanly to the caller.
Quoted 22 lines
mm/swapfile.c +2
-1+ }if (unlikely (!folio_test_swapcache (folio))) -return -ENOMEM ; +return -EAGAIN ;return 0 ; }mm/vmscan.c +2
-0@@ -1257,6 +1257,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,*/ if (folio_test_anon (folio) &&folio_test_swapbacked (folio) && !folio_test_swapcache (folio)) { +int ret; +if (!(sc->gfp_mask & __GFP_IO)) goto keep_locked;if (folio_maybe_dma_pinned (folio))
mm/vmscan.c static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1280
Quoted 5 lines
mm/vmscan.c +1
-1@@ -1275,10 +1277,10 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, split_folio_to_list(folio, folio_list)) goto activate_locked; } -if (folio_alloc_swap (folio)) { +if ((ret =folio_alloc_swap (folio))) {
I prefer doing the assignment outside the conditional.
Quoted 9 lines
mm/vmscan.c +1
-1int __maybe_unused order =folio_order (folio); -if (!folio_test_large (folio)) +if (!folio_test_large (folio) || ret != -EAGAIN ) goto activate_locked_split;/* Fallback to swap normal pages */ if (split_folio_to_list (folio, folio_list))What’s your view on this, David?
I guess returning from folio_alloc_swap() whether a split could allow for swapout (e.g., -E2BIG) would be reasonable.
To catch all the cases where it makes a difference:
* No free swap space (split won't work)
* Some free swap space (split would work)
* Sufficient free swap space, but fragmented (split would work)
* No memcg space (split won't work)
* Some memcg space (split would work)
Signature · 3 lines
Cheers, David
Barry Song
reviewerQuoted 47 lines · David Hildenbrand (Arm)
Quoted 36 lines · Barry Song (Xiaomi)
On Fri, Jun 19, 2026 at 10:04 PM David Hildenbrand (Arm) <[email protected]> wrote:
[...]Quoted 14 lines
Review static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1293
Quoted 9 lines
/* * The page can not be swapped. *(quoted diff) +2
-0@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, if (!folio_test_large(folio)) goto activate_locked_split; + if (!__can_reclaim_anon_pages(memcg, sc)) + goto activate_locked_split;Why are we even trying to allocate swap space if we cannot reclaim such pages? Makes we wonder whether we would want to have that check earlier, before the
folio_alloc_swap().Quoted 1 line
Any downsides?
I don't think there are any obvious downsides there. One issue is that the memcg may not be passed from
reclaim_pages(), so memcg would always be NULL. However, the folio could still belong to a memcg whose swap quota has been exhausted. In that case, my__can_reclaim_anon_pages()will fail when checking whether we can swap out. But switching tofolio_memcg()also seems awkward.So I feel Kairui’s suggestion [1] might be the best approach. In
folio_alloc_swap(), we return-EAGAINto tellvmscan.cthat we can split the folio and retry the swap-out.
only when there are sufficient swap slots and sufficient memcg swap quota do we return-EAGAIN, allowing vmscan to perform a split.mm/swapfile.c +0
-0mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1760
Quoted 6 lines
mm/swapfile.c +3
-0@@ -1755,6 +1755,9 @@ int folio_alloc_swap(struct folio *folio)VM_WARN_ON_ONCE (1 );return -EINVAL ; } + +if (get_nr_swap_pages () < (1 << order)) +return -ENOMEM ;I guess we could be clearer with the return value:
Quoted 7 lines
-> !get_nr_swap_pages() ->
-ENOSPC/-ENOMEM(no space at all)
->
get_nr_swap_pages()< (1 << order) ->-E2BIG(there is some space, but not for the full thing)
understood. make sense.
Quoted 3 lines
But now I wonder whether we would also want to check "is there any free swap space", not just "is there any swap".
I don't quite understand you. get_nr_swap_pages() returns nr_swap_pages, which increases or decreases as swap is allocated or freed. I guess it just reflects how many swaps we currently have available?
Quoted 19 lines
Essentially, try returning
-E2BIGif there is the chance to swap out after split, and-ENOSPC/-ENOMEMif a split wouldn't help.mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1777
Quoted 11 lines
mm/swapfile.c +2
-1} again:@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ;Here we wouldn't have the information whether we could charge after a split.
Quoted 1 line
So that would require a rework to signal this more cleanly to the caller.
Yep. The tricky part is that mem_cgroup_try_charge_swap() cannot return how much swap quota is available in the memcg. Do you prefer to add an output argument to mem_cgroup_try_charge_swap() to expose that, or should we introduce a separate wrapper such as …
long get_nr_swap_pages_from_folio_memcg(struct folio *folio)
{
long int nr;
memcg = get_memcg_from_folio(folio);
nr = mem_cgroup_get_nr_swap_pages(memcg);
return nr;
}then in folio_alloc_swap(), if nr < folio_nr_pages() but > 0, we ask for a split by returning -E2BIG ?
Quoted 22 lines
Quoted 22 lines
mm/swapfile.c +2
-1+ }if (unlikely (!folio_test_swapcache (folio))) -return -ENOMEM ; +return -EAGAIN ;return 0 ; }mm/vmscan.c +2
-0@@ -1257,6 +1257,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,*/ if (folio_test_anon (folio) &&folio_test_swapbacked (folio) && !folio_test_swapcache (folio)) { +int ret; +if (!(sc->gfp_mask & __GFP_IO)) goto keep_locked;if (folio_maybe_dma_pinned (folio))
Quoted 8 lines
mm/vmscan.c static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1280
Quoted 6 lines
mm/vmscan.c +1
-1@@ -1275,10 +1277,10 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, split_folio_to_list(folio, folio_list)) goto activate_locked; } -if (folio_alloc_swap (folio)) { +if ((ret =folio_alloc_swap (folio))) {I prefer doing the assignment outside the conditional.
Ok.
Quoted 14 lines
Quoted 10 lines
mm/vmscan.c +1
-1int __maybe_unused order =folio_order (folio); -if (!folio_test_large (folio)) +if (!folio_test_large (folio) || ret != -EAGAIN ) goto activate_locked_split;/* Fallback to swap normal pages */ if (split_folio_to_list (folio, folio_list))What’s your view on this, David?
I guess returning from
folio_alloc_swap()whether a split could allow for swapout (e.g.,-E2BIG) would be reasonable.
Quoted 6 lines
To catch all the cases where it makes a difference:
* No free swap space (split won't work)
* Some free swap space (split would work)
* Sufficient free swap space, but fragmented (split would work)
* No memcg space (split won't work)
* Some memcg space (split would work)
Right. The memcg part is the tricky one, since we don’t have that information available.
Best Regards
Barry
David Hildenbrand (Arm)
maintainerQuoted 8 lines
Quoted 3 lines
But now I wonder whether we would also want to check "is there any free swap space", not just "is there any swap".
I don't quite understand you.
get_nr_swap_pages()returnsnr_swap_pages, which increases or decreases as swap is allocated or freed. I guess it just reflects how many swaps we currently have available?
Indeed, I was confused by the function name it's "free swap pages". So all goof :)
Quoted 20 lines
Quoted 20 lines
Essentially, try returning
-E2BIGif there is the chance to swap out after split, and-ENOSPC/-ENOMEMif a split wouldn't help.mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1777
Quoted 11 lines
}again:
mm/swapfile.c +2
-1@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ;Here we wouldn't have the information whether we could charge after a split.
Quoted 6 lines
Quoted 1 line
So that would require a rework to signal this more cleanly to the caller.
Yep. The tricky part is that
mem_cgroup_try_charge_swap()cannot return how much swap quota is available in the memcg. Do you prefer to add an output argument tomem_cgroup_try_charge_swap()to expose that
That would probably be cleanest, if that is easily possible. We would want to get memcg maintainer feedback on that.
@memcg folks: we'd like to know whether splitting a large folio would make mem_cgroup_try_charge_swap() succeed on a split (smaller) part, to distinguish "there is no way we can swap out anything, don't split" vs. "we could swap out, split".
What's the best way to obtain that information?
Signature · 3 lines
Cheers, David
Johannes Weiner
maintainerQuoted 37 lines · David Hildenbrand (Arm)
Quoted 8 lines
Quoted 3 lines
But now I wonder whether we would also want to check "is there any free swap space", not just "is there any swap".
I don't quite understand you.
get_nr_swap_pages()returnsnr_swap_pages, which increases or decreases as swap is allocated or freed. I guess it just reflects how many swaps we currently have available?Indeed, I was confused by the function name it's "free swap pages". So all goof :)
Quoted 20 lines
Quoted 20 lines
Essentially, try returning
-E2BIGif there is the chance to swap out after split, and-ENOSPC/-ENOMEMif a split wouldn't help.mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1777
Quoted 11 lines
}again:
mm/swapfile.c +2
-1@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ;Here we wouldn't have the information whether we could charge after a split.
Quoted 6 lines
Quoted 1 line
So that would require a rework to signal this more cleanly to the caller.
Yep. The tricky part is that
mem_cgroup_try_charge_swap()cannot return how much swap quota is available in the memcg. Do you prefer to add an output argument tomem_cgroup_try_charge_swap()to expose that
Quoted 7 lines
That would probably be cleanest, if that is easily possible. We would want to get memcg maintainer feedback on that.
@memcg folks: we'd like to know whether splitting a large folio would make
mem_cgroup_try_charge_swap()succeed on a split (smaller) part, to distinguish "there is no way we can swap out anything, don't split" vs. "we could swap out, split".
It's technically doable, but is this worth the bother? The remaining headroom is less than a large folio. You can split this one, but you cannot even swap out all of its subpages anymore? From the cgroup side, we don't need the limit to be obeyed this rigidly. We overcharge temporarily in other places if it's convenient to do so. A fuzz factor around the limit is acceptable.
But if you still want to do it, here is how:
The page_counter_try_charge() in __mem_cgroup_try_charge_swap() walks the hierarchy upwards. If it fails, it will store the first level that failed against its limit. You can do the mem_cgroup_margin() math against this counter to determine headroom. An ancestor *could* be more restrictive, so you need to finish the hierarchy walk to the root and use the min() of all the swap.max - page_counter_read(swap). Then return that in a return argument from __mem_cgroup_try_charge_swap().
David Hildenbrand (Arm)
maintainerQuoted 18 lines · Johannes Weiner
Quoted 17 lines · David Hildenbrand (Arm)
Quoted 5 lines
I don't quite understand you.
get_nr_swap_pages()returnsnr_swap_pages, which increases or decreases as swap is allocated or freed. I guess it just reflects how many swaps we currently have available?Indeed, I was confused by the function name it's "free swap pages". So all goof :)
Quoted 6 lines
Yep. The tricky part is that
mem_cgroup_try_charge_swap()cannot return how much swap quota is available in the memcg. Do you prefer to add an output argument tomem_cgroup_try_charge_swap()to expose thatThat would probably be cleanest, if that is easily possible. We would want to get memcg maintainer feedback on that.
Quoted 8 lines
Quoted 4 lines
@memcg folks: we'd like to know whether splitting a large folio would make
mem_cgroup_try_charge_swap()succeed on a split (smaller) part, to distinguish "there is no way we can swap out anything, don't split" vs. "we could swap out, split".It's technically doable, but is this worth the bother? The remaining headroom is less than a large folio. You can split this one, but you cannot even swap out all of its subpages anymore?
I was asking myself the same, but when we think in terms of THPs on arm64 64k we're in the range of double-digit MiBs.
Quoted 4 lines
From the cgroup
side, we don't need the limit to be obeyed this rigidly. We overcharge temporarily in other places if it's convenient to do so. A fuzz factor around the limit is acceptable.
Thanks for that information.
Quoted 10 lines
But if you still want to do it, here is how:
The
page_counter_try_charge()in__mem_cgroup_try_charge_swap()walks the hierarchy upwards. If it fails, it will store the first level that failed against its limit. You can do themem_cgroup_margin()math against this counter to determine headroom. An ancestor *could* be more restrictive, so you need to finish the hierarchy walk to the root and use themin()of all the swap.max -page_counter_read(swap). Then return that in a return argument from__mem_cgroup_try_charge_swap().
Thanks! @Barry, up to you if we want to implement that right away or if we're simply going to assume that if charging fails, not worth splitting (changing the existing handling IIUC).
Signature · 3 lines
Cheers, David
Barry Song
reviewerQuoted 25 lines · David Hildenbrand (Arm)
Quoted 23 lines · Johannes Weiner
Quoted 21 lines · David Hildenbrand (Arm)
Quoted 5 lines
I don't quite understand you.
get_nr_swap_pages()returnsnr_swap_pages, which increases or decreases as swap is allocated or freed. I guess it just reflects how many swaps we currently have available?Indeed, I was confused by the function name it's "free swap pages". So all goof :)
Quoted 6 lines
Yep. The tricky part is that
mem_cgroup_try_charge_swap()cannot return how much swap quota is available in the memcg. Do you prefer to add an output argument tomem_cgroup_try_charge_swap()to expose thatThat would probably be cleanest, if that is easily possible. We would want to get memcg maintainer feedback on that.
@memcg folks: we'd like to know whether splitting a large folio would make
mem_cgroup_try_charge_swap()succeed on a split (smaller) part, to distinguish "there is no way we can swap out anything, don't split" vs. "we could swap out, split".
Quoted 6 lines
Quoted 3 lines
It's technically doable, but is this worth the bother? The remaining headroom is less than a large folio. You can split this one, but you cannot even swap out all of its subpages anymore?
I was asking myself the same, but when we think in terms of THPs on arm64 64k we're in the range of double-digit MiBs.
Yep. But we haven't enabled thp_swp for 64KB base pages yet; there is an ongoing attempt[1].
The current concern is that such large folios may incur significant latency for I/O or compression, and reclaim could end up waiting for a long time.
For huge folios, maybe we should split them into 2MB folios instead of splitting all the way down to base pages when swapping out.
That way, we could allow architectures with "huge" large folios to swap out using THP_SWAP?
BTW, PowerPC is enabling THP_SWPOUT for up to 16MB THP[2].
[1] https://lore.kernel.org/linux-mm/[email protected]/
[2] https://lore.kernel.org/linux-mm/[email protected]/
Quoted 19 lines
Quoted 5 lines
From the cgroup
side, we don't need the limit to be obeyed this rigidly. We overcharge temporarily in other places if it's convenient to do so. A fuzz factor around the limit is acceptable.Thanks for that information.
Quoted 10 lines
But if you still want to do it, here is how:
The
page_counter_try_charge()in__mem_cgroup_try_charge_swap()walks the hierarchy upwards. If it fails, it will store the first level that failed against its limit. You can do themem_cgroup_margin()math against this counter to determine headroom. An ancestor *could* be more restrictive, so you need to finish the hierarchy walk to the root and use themin()of all the swap.max -page_counter_read(swap). Then return that in a return argument from__mem_cgroup_try_charge_swap().
It seems it is exactly what mem_cgroup_get_nr_swap_pages() is doing:
long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg)
{
long nr_swap_pages = get_nr_swap_pages();
if (mem_cgroup_disabled() || do_memsw_account())
return nr_swap_pages;
for (; !mem_cgroup_is_root(memcg); memcg = parent_mem_cgroup(memcg))
nr_swap_pages = min_t(long, nr_swap_pages,
READ_ONCE(memcg->swap.max) -
page_counter_read(&memcg->swap));
return nr_swap_pages;
}Quoted 3 lines
Thanks! @Barry, up to you if we want to implement that right away or if we're simply going to assume that if charging fails, not worth splitting (changing the existing handling IIUC).
Thanks very much to Johannes and David for the help.
Hi Johannes, I wonder if you're comfortable with something like the code below (just as a concept):
include/linux/swap.h +0 -0
@@ -573,12 +573,12 @@ static inline void
folio_throttle_swaprate(struct folio *folio, gfp_t gfp)
#endif
#if defined(CONFIG_MEMCG) && defined(CONFIG_SWAP)
-int __mem_cgroup_try_charge_swap(struct folio *folio); -static inline int mem_cgroup_try_charge_swap(struct folio *folio) +int __mem_cgroup_try_charge_swap(struct folio *folio, long *left_space); +static inline int mem_cgroup_try_charge_swap(struct folio *folio, long *left_space)
{
if (mem_cgroup_disabled())
return 0;- return __mem_cgroup_try_charge_swap(folio);
+ return __mem_cgroup_try_charge_swap(folio, left_space);
}
extern void __mem_cgroup_uncharge_swap(unsigned short id, unsigned
int nr_pages);
mm/memcontrol.c +5 -1
@@ -5578,7 +5578,7 @@ int __init mem_cgroup_init(void)* * Returns 0 on success, -ENOMEM on failure. */ -int __mem_cgroup_try_charge_swap (struct folio *folio) +int __mem_cgroup_try_charge_swap (struct folio *folio,long *left_space) {unsigned int nr_pages =folio_nr_pages (folio);struct swap_cluster_info *ci;
@@ -5611,6 +5611,10 @@ int __mem_cgroup_try_charge_swap(struct folio *folio)memcg_memory_event (memcg,MEMCG_SWAP_MAX );memcg_memory_event (memcg,MEMCG_SWAP_FAIL );mem_cgroup_private_id_put (memcg, nr_pages); +if (folio_test_large (folio)) + *left_space =mem_cgroup_get_nr_swap_pages (memcg); +else + *left_space =0 ;return -ENOMEM ; }mod_memcg_state (memcg,MEMCG_SWAP , nr_pages);
Thanks
Barry
Johannes Weiner
maintainerQuoted 12 lines · Barry Song
mm/memcontrol.c +1
-1@@ -5578,7 +5578,7 @@ int __init mem_cgroup_init(void)* * Returns 0 on success, -ENOMEM on failure. */ -int __mem_cgroup_try_charge_swap (struct folio *folio) +int __mem_cgroup_try_charge_swap (struct folio *folio,long *left_space) {unsigned int nr_pages =folio_nr_pages (folio);struct swap_cluster_info *ci;
mm/memcontrol.c int __mem_cgroup_try_charge_swap(struct folio *folio) · line 5615
Quoted 5 lines
mm/memcontrol.c +2
-0@@ -5611,6 +5611,10 @@ int __mem_cgroup_try_charge_swap(struct folio *folio)memcg_memory_event (memcg,MEMCG_SWAP_MAX );memcg_memory_event (memcg,MEMCG_SWAP_FAIL );mem_cgroup_private_id_put (memcg, nr_pages); +if (folio_test_large (folio)) + *left_space =mem_cgroup_get_nr_swap_pages (memcg);
It's a bit awkward to walk up the whole hierarchy again when we already have the counter that failed. Please do something like this (not tested!), then use page_counter_margin() against @counter:
---
include/linux/page_counter.h +1 -0
@@ -68,6 +68,7 @@ static inline unsigned long page_counter_read(struct page_counter *counter)return atomic_long_read (&counter->usage); } +long page_counter_margin (struct page_counter *counter);void page_counter_cancel (struct page_counter *counter,unsigned long nr_pages);void page_counter_charge (struct page_counter *counter,unsigned long nr_pages); bool page_counter_try_charge(struct page_counter *counter,
mm/memcontrol.c +3 -6
@@ -5275,12 +5275,9 @@ long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg) {long nr_swap_pages =get_nr_swap_pages (); -if (mem_cgroup_disabled () ||do_memsw_account ()) -return nr_swap_pages; -for (; !mem_cgroup_is_root (memcg); memcg =parent_mem_cgroup (memcg)) - nr_swap_pages =min_t (long, nr_swap_pages, -READ_ONCE (memcg->swap.max) - -page_counter_read (&memcg->swap)); +if (!mem_cgroup_disabled () && !do_memsw_account ()) + nr_swap_pages =min (nr_swap_pages,page_counter_margin (&memcg->swap)); +return nr_swap_pages; }
mm/page_counter.c +16 -0
@@ -46,6 +46,22 @@ static void propagate_protected_usage(struct page_counter *c, } } +/** +* page_counter_margin - remaining usable space within hierarchical limits +* @counter: counter +*/ +long page_counter_margin (struct page_counter *counter) +{ +long margin =PAGE_COUNTER_MAX ; + +do { +long m =READ_ONCE (counter->max) -page_counter_read (counter); + margin =min (margin, m); + }while ((counter = counter->parent)); + +return margin; +} +/** * page_counter_cancel - take pages out of the local counter * @counter: counter
Barry Song
reviewerQuoted 15 lines · Johannes Weiner
Quoted 13 lines · Barry Song
mm/memcontrol.c +1
-1@@ -5578,7 +5578,7 @@ int __init mem_cgroup_init(void)* * Returns 0 on success, -ENOMEM on failure. */ -int __mem_cgroup_try_charge_swap (struct folio *folio) +int __mem_cgroup_try_charge_swap (struct folio *folio,long *left_space) {unsigned int nr_pages =folio_nr_pages (folio);struct swap_cluster_info *ci;@@ -5611,6 +5611,10 @@ int __mem_cgroup_try_charge_swap(struct folio *folio)memcg_memory_event (memcg,MEMCG_SWAP_MAX );
Quoted 8 lines
mm/memcontrol.c int __mem_cgroup_try_charge_swap(struct folio *folio) · line 5615
Quoted 4 lines
mm/memcontrol.c +2
-0memcg_memory_event (memcg,MEMCG_SWAP_FAIL );mem_cgroup_private_id_put (memcg, nr_pages); +if (folio_test_large (folio)) + *left_space =mem_cgroup_get_nr_swap_pages (memcg);It's a bit awkward to walk up the whole hierarchy again when we already have the counter that failed. Please do something like this (not tested!), then use
page_counter_margin()against @counter:
Thanks very much for your suggestion, Johannes! Sorry for the late reply. I've been quite busy recently, so I asked Xueyuan Chen (Cc'ed) to help send RFC v2, which will include your suggestion.
Best Regards
Barry
Quoted 84 lines · Barry Song (Xiaomi)
When swap is disabled or exhausted, swap slot allocation may fail during swapout, causing large folios to be split into small folios. The splitting is reasonable when we truly fail to obtain contiguous swap slots, but it is pointless in the no-space case.
A simple way to reproduce this is to invoke
MADV_PAGEOUTon a system with mTHP enabled but without swap configured.#define SIZE (16 * 1024 * 1024)
int main(void){ char *buf = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); memset(buf, 1, SIZE); madvise(buf, SIZE, MADV_PAGEOUT); munmap(buf, SIZE); return 0;}
With 16KB mTHP enabled, we observe:
~ # cat/sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split1024This patch checks swap space before splitting. If there is no available space, it skips splitting. After the patch, we observe:
~ # cat/sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split0Reported-by Nanzhe Zhao <[email protected]>Cc David Hildenbrand <[email protected]>Cc Lorenzo Stoakes <[email protected]>Cc Zi Yan <[email protected]>Cc Baolin Wang <[email protected]>Cc Liam R. Howlett <[email protected]>Cc Nico Pache <[email protected]>Cc Ryan Roberts <[email protected]>Cc Dev Jain <[email protected]>Cc Lance Yang <[email protected]>Cc Kairui Song <[email protected]>Cc Qi Zheng <[email protected]>Cc Shakeel Butt <[email protected]>Cc Axel Rasmussen <[email protected]>Cc Yuanchu Xie <[email protected]>Cc Wei Xu <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>--- mm/vmscan.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)mm/vmscan.c +11
-2@@ -339,8 +339,7 @@ static bool can_demote(int nid, struct scan_control *sc,return !nodes_empty (allowed_mask); } -static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, -int nid, +static inline bool __can_reclaim_anon_pages (struct mem_cgroup *memcg,struct scan_control *sc) {if (memcg ==NULL ) {@@ -356,6 +355,16 @@ static inline bool can_reclaim_anon_pages(struct mem_cgroup *memcg,return true; } +return false; +} + +static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, +int nid, +struct scan_control *sc) +{ +if (__can_reclaim_anon_pages (memcg, sc)) +return true; +/* * The page can not be swapped. *
mm/vmscan.c static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1296
Quoted 8 lines
mm/vmscan.c +2
-0@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,if (!folio_test_large (folio)) goto activate_locked_split; +if (!__can_reclaim_anon_pages (memcg, sc)) + goto activate_locked_split;/* Fallback to swap normal pages */ if (split_folio_to_list (folio, folio_list)) goto activate_locked;
Hello Barry,
Thanks for raising this issue. I saw a similar issue report in the mail list before and was thinking that, perhaps another approach is to let folio_alloc_swap return a more detailed error code, for example:
- 1. the mem_cgroup_try_charge_swap in it failed
- 2. allocation failed but nr_swap_pages > folio size
- 3. allocation failed because all devices are full or unusable (roughly nr_swap_pages < folio size)
Only case 2 requires splitting. __can_reclaim_anon_pages also checks demote which is not related to swap.
Barry Song (Xiaomi)
reviewerQuoted 97 lines · Kairui Song
Quoted 84 lines · Barry Song (Xiaomi)
When swap is disabled or exhausted, swap slot allocation may fail during swapout, causing large folios to be split into small folios. The splitting is reasonable when we truly fail to obtain contiguous swap slots, but it is pointless in the no-space case.
A simple way to reproduce this is to invoke
MADV_PAGEOUTon a system with mTHP enabled but without swap configured.#define SIZE (16 * 1024 * 1024)
int main(void)
{
char *buf =mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);memset(buf, 1, SIZE);
madvise(buf, SIZE,MADV_PAGEOUT);munmap(buf, SIZE);
return 0;
}With 16KB mTHP enabled, we observe:
~ # cat/sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split1024This patch checks swap space before splitting. If there is no available space, it skips splitting. After the patch, we observe:
~ # cat/sys/kernel/mm/transparent_hugepage/hugepages-16kB/stats/split0Reported-by Nanzhe Zhao <[email protected]>Cc David Hildenbrand <[email protected]>Cc Lorenzo Stoakes <[email protected]>Cc Zi Yan <[email protected]>Cc Baolin Wang <[email protected]>Cc Liam R. Howlett <[email protected]>Cc Nico Pache <[email protected]>Cc Ryan Roberts <[email protected]>Cc Dev Jain <[email protected]>Cc Lance Yang <[email protected]>Cc Kairui Song <[email protected]>Cc Qi Zheng <[email protected]>Cc Shakeel Butt <[email protected]>Cc Axel Rasmussen <[email protected]>Cc Yuanchu Xie <[email protected]>Cc Wei Xu <[email protected]>Signed-off-by Barry Song (Xiaomi) <[email protected]>---
mm/vmscan.c | 15 +++++++++++++--1 file changed, 13 insertions(+), 2 deletions(-)mm/vmscan.c +11
-2@@ -339,8 +339,7 @@ static bool can_demote(int nid, struct scan_control *sc, return !nodes_empty(allowed_mask); } -static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, - int nid, +static inline bool __can_reclaim_anon_pages (struct mem_cgroup *memcg, struct scan_control *sc) { if (memcg == NULL) {@@ -356,6 +355,16 @@ static inline bool can_reclaim_anon_pages(struct mem_cgroup *memcg, return true; } + return false; +} + +static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, + int nid, + struct scan_control *sc) +{ + if (__can_reclaim_anon_pages(memcg, sc)) + return true; + /** The page can not be swapped. * mm/vmscan.c static unsigned int shrink_folio_list(struct list_head *folio_list, · line 1296
Quoted 8 lines
mm/vmscan.c +2
-0@@ -1280,6 +1289,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, if (!folio_test_large(folio)) goto activate_locked_split; + if (!__can_reclaim_anon_pages(memcg, sc)) + goto activate_locked_split; /* Fallback to swap normal pages */ if (split_folio_to_list(folio, folio_list)) goto activate_locked;Hello Barry,
Quoted 2 lines
Thanks for raising this issue. I saw a similar issue report in the mail list before and was thinking that, perhaps another approach is to
Hi Kairui,
Could you please post the link to your report? I'd like to add your Reported-by and Closes tags as well.
Quoted 7 lines
let
folio_alloc_swapreturn a more detailed error code, for example:- 1. the
mem_cgroup_try_charge_swapin it failed
- 2. allocation failed butnr_swap_pages> folio size
- 3. allocation failed because all devices are full or unusable (roughlynr_swap_pages< folio size)
folio_alloc_swap() returns error codes such as -EAGAIN, -EINVAL, and -ENOMEM. For cases 1, 2, and 3, I assume it would return -ENOMEM?
I assume you mean that we might want folio_alloc_swap() to return an enum instead?
another approach is that I can return -EAGAIN for those cases we want to retry swapping-out after splitting folios:
mm/swapfile.c +7 -2
@@ -1755,6 +1755,9 @@ int folio_alloc_swap(struct folio *folio)VM_WARN_ON_ONCE (1 );return -EINVAL ; } + +if (get_nr_swap_pages () < (1 << order)) +return -ENOMEM ; } again:
@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ; + }if (unlikely (!folio_test_swapcache (folio))) -return -ENOMEM ; +return -EAGAIN ;return 0 ; }
mm/vmscan.c +16 -15
@@ -1257,6 +1257,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,*/ if (folio_test_anon (folio) &&folio_test_swapbacked (folio) && !folio_test_swapcache (folio)) { +int ret; +if (!(sc->gfp_mask & __GFP_IO)) goto keep_locked;if (folio_maybe_dma_pinned (folio))
@@ -1275,25 +1277,24 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, split_folio_to_list(folio, folio_list)) goto activate_locked; } -if (folio_alloc_swap (folio)) { -int __maybe_unused order =folio_order (folio); + ret =folio_alloc_swap (folio); +if (!folio_test_large (folio) || ret != -EAGAIN ) + goto activate_locked_split; -if (!folio_test_large (folio)) - goto activate_locked_split; -/* Fallback to swap normal pages */ -if (split_folio_to_list (folio, folio_list)) - goto activate_locked; +/* Fallback to swap normal pages */ +if (split_folio_to_list (folio, folio_list)) + goto activate_locked;#ifdef CONFIG_TRANSPARENT_HUGEPAGE -if (nr_pages >=HPAGE_PMD_NR ) { -count_memcg_folio_events (folio, +if (nr_pages >=HPAGE_PMD_NR ) { +count_memcg_folio_events (folio,THP_SWPOUT_FALLBACK ,1 ); -count_vm_event (THP_SWPOUT_FALLBACK ); - } -#endif -count_mthp_stat (order,MTHP_STAT_SWPOUT_FALLBACK ); -if (folio_alloc_swap (folio)) - goto activate_locked_split; +count_vm_event (THP_SWPOUT_FALLBACK ); } +#endif +count_mthp_stat (folio_order (folio),MTHP_STAT_SWPOUT_FALLBACK ); +if (folio_alloc_swap (folio)) + goto activate_locked_split; +/* * Normally the folio will be dirtied in unmap because * its pte should be dirty. A special case is MADV_FREE
Quoted 2 lines
Only case 2 requires splitting.
__can_reclaim_anon_pagesalso checks demote which is not related to swap.
I actually extracted __can_reclaim_anon_pages(), which only checks swap, whereas can_reclaim_anon_pages() checks both swap and demotion. :-)
Best Regards
Barry
Quoted 6 lines · Barry Song (Xiaomi)
Quoted 4 lines · Kairui Song
Hello Barry,
Quoted 7 lines
Quoted 2 lines
Thanks for raising this issue. I saw a similar issue report in the mail list before and was thinking that, perhaps another approach is to
Hi Kairui,
Could you please post the link to your report? I'd like to add your Reported-by and Closes tags as well.
Hello, here is my previous reply:
https://lore.kernel.org/linux-mm/CAMgjq7AD2ivdgD_kRx+0+w0M=Fvz5dXepw84_Ui_q7=97B4Ofw@mail.gmail.com/
It's the same issue, not reported by me though.
Quoted 13 lines
Quoted 8 lines
let
folio_alloc_swapreturn a more detailed error code, for example:- 1. the
mem_cgroup_try_charge_swapin it failed
- 2. allocation failed butnr_swap_pages> folio size
- 3. allocation failed because all devices are full or unusable (roughlynr_swap_pages< folio size)
folio_alloc_swap()returns error codes such as-EAGAIN,-EINVAL, and-ENOMEM. For cases 1, 2, and 3, I assume it would return-ENOMEM?
Quoted 5 lines
I assume you mean that we might want
folio_alloc_swap()to return an enum instead?another approach is that I can return
-EAGAINfor those cases we want to retry swapping-out after splitting folios:
Right, I don't have a strong preference on this.
Quoted 84 lines
mm/swapfile.c +7
-2@@ -1755,6 +1755,9 @@ int folio_alloc_swap(struct folio *folio)VM_WARN_ON_ONCE (1 );return -EINVAL ; } + +if (get_nr_swap_pages () < (1 << order)) +return -ENOMEM ; } again:@@ -1769,11 +1772,13 @@ int folio_alloc_swap(struct folio *folio) }/* Need to call this even if allocation failed, for MEMCG_SWAP_FAIL. */ -if (unlikely (mem_cgroup_try_charge_swap (folio))) +if (unlikely (mem_cgroup_try_charge_swap (folio))) {swap_cache_del_folio (folio); +return -ENOMEM ; + }if (unlikely (!folio_test_swapcache (folio))) -return -ENOMEM ; +return -EAGAIN ;return 0 ; }mm/vmscan.c +16
-15@@ -1257,6 +1257,8 @@ static unsigned int shrink_folio_list(struct list_head *folio_list,*/ if (folio_test_anon (folio) &&folio_test_swapbacked (folio) && !folio_test_swapcache (folio)) { +int ret; +if (!(sc->gfp_mask & __GFP_IO)) goto keep_locked;if (folio_maybe_dma_pinned (folio))@@ -1275,25 +1277,24 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, split_folio_to_list(folio, folio_list)) goto activate_locked; } -if (folio_alloc_swap (folio)) { -int __maybe_unused order =folio_order (folio); + ret =folio_alloc_swap (folio); +if (!folio_test_large (folio) || ret != -EAGAIN ) + goto activate_locked_split; -if (!folio_test_large (folio)) - goto activate_locked_split; -/* Fallback to swap normal pages */ -if (split_folio_to_list (folio, folio_list)) - goto activate_locked; +/* Fallback to swap normal pages */ +if (split_folio_to_list (folio, folio_list)) + goto activate_locked;#ifdef CONFIG_TRANSPARENT_HUGEPAGE -if (nr_pages >=HPAGE_PMD_NR ) { -count_memcg_folio_events (folio, +if (nr_pages >=HPAGE_PMD_NR ) { +count_memcg_folio_events (folio,THP_SWPOUT_FALLBACK ,1 ); -count_vm_event (THP_SWPOUT_FALLBACK ); - } -#endif -count_mthp_stat (order,MTHP_STAT_SWPOUT_FALLBACK ); -if (folio_alloc_swap (folio)) - goto activate_locked_split; +count_vm_event (THP_SWPOUT_FALLBACK ); } +#endif +count_mthp_stat (folio_order (folio),MTHP_STAT_SWPOUT_FALLBACK ); +if (folio_alloc_swap (folio)) + goto activate_locked_split; +/* * Normally the folio will be dirtied in unmap because * its pte should be dirty. A special case is MADV_FREE
Quoted 6 lines
Quoted 2 lines
Only case 2 requires splitting.
__can_reclaim_anon_pagesalso checks demote which is not related to swap.I actually extracted
__can_reclaim_anon_pages(), which only checks swap, whereascan_reclaim_anon_pages()checks both swap and demotion. :-)
Ah yeah you are right, I didn't see that part.