support batched checks of the references for large folios
Subsystems (4)
cover support batched checks of the references for large folios
Currently, folio_referenced_one() always checks the young flag for each PTE sequentially, which is inefficient for large folios. This inefficiency is especially noticeable when reclaiming clean file-backed large folios, where folio_referenced() is observed as a significant performance hotspot.
Moreover, on Arm architecture, which supports contiguous PTEs, there is already an optimization to clear the young flags for PTEs within a contiguous range. However, this is not sufficient. We can extend this to perform batched operations for the entire large folio (which might exceed the contiguous range: CONT_PTE_SIZE).
By supporting batched checking of the young flags and flushing TLB entries, I observed a 33% performance improvement in my file-backed folios reclaim tests.
BTW, I still noticed a hotspot in try_to_unmap() in my test. Hope Barry can resend the optimization patch for try_to_unmap() [1].
[1] https://lore.kernel.org/all/[email protected]/
Baolin Wang (2):
arm64: mm: support batch clearing of the young flag for large folios
mm: rmap: support batched checks of the references for large folios
arch/arm64/include/asm/pgtable.h | 23 ++++++++++++----- arch/arm64/mm/contpte.c | 44 ++++++++++++++++++++++---------- include/linux/mmu_notifier.h | 9 ++++--- include/linux/pgtable.h | 19 ++++++++++++++ mm/rmap.c | 22 ++++++++++++++-- 5 files changed, 92 insertions(+), 25 deletions(-)
Signature · 1 line
2.47.3
PATCH 1/2 arm64: mm: support batch clearing of the young flag for large folios
Currently, contpte_ptep_test_and_clear_young() and contpte_ptep_clear_flush_young() only clear the young flag and flush TLBs for PTEs within the contiguous range. To support batch PTE operations for other sized large folios in the following patches, adding a new parameter to specify the number of PTEs.
While we are at it, rename the functions to maintain consistency with other contpte_*() functions.
--- arch/arm64/include/asm/pgtable.h | 12 ++++----- arch/arm64/mm/contpte.c | 44 ++++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 19 deletions(-)
arch/arm64/include/asm/pgtable.h +6 -6
@@ -1679,10 +1679,10 @@ extern void contpte_clear_full_ptes(struct mm_struct *mm, unsigned long addr,extern pte_t contpte_get_and_clear_full_ptes (struct mm_struct *mm,unsigned long addr,pte_t *ptep,unsigned int nr,int full); -extern int contpte_ptep_test_and_clear_young (struct vm_area_struct *vma, -unsigned long addr,pte_t *ptep); -extern int contpte_ptep_clear_flush_young (struct vm_area_struct *vma, -unsigned long addr,pte_t *ptep); +extern int contpte_test_and_clear_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep,unsigned int nr); +extern int contpte_clear_flush_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep,unsigned int nr);extern void contpte_wrprotect_ptes (struct mm_struct *mm,unsigned long addr,pte_t *ptep,unsigned int nr); extern int contpte_ptep_set_access_flags(struct vm_area_struct *vma,
@@ -1854,7 +1854,7 @@ static inline int ptep_test_and_clear_young(struct vm_area_struct *vma,if (likely (!pte_valid_cont (orig_pte)))return __ptep_test_and_clear_young (vma, addr, ptep); -return contpte_ptep_test_and_clear_young (vma, addr, ptep); +return contpte_test_and_clear_young_ptes (vma, addr, ptep,CONT_PTES ); }#define __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH
@@ -1866,7 +1866,7 @@ static inline int ptep_clear_flush_young(struct vm_area_struct *vma,if (likely (!pte_valid_cont (orig_pte)))return __ptep_clear_flush_young (vma, addr, ptep); -return contpte_ptep_clear_flush_young (vma, addr, ptep); +return contpte_clear_flush_young_ptes (vma, addr, ptep,CONT_PTES ); }#define wrprotect_ptes wrprotect_ptes
arch/arm64/mm/contpte.c +31 -13
78 lines not on this page · raw message
Signature · 1 line
2.47.3
PATCH 2/2 mm: rmap: support batched checks of the references for large folios
In reply to Baolin WangCurrently, folio_referenced_one() always checks the young flag for each PTE sequentially, which is inefficient for large folios. This inefficiency is especially noticeable when reclaiming clean file-backed large folios, where folio_referenced() is observed as a significant performance hotspot.
Moreover, on Arm architecture, which supports contiguous PTEs, there is already an optimization to clear the young flags for PTEs within a contiguous range. However, this is not sufficient. We can extend this to perform batched operations for the entire large folio (which might exceed the contiguous range: CONT_PTE_SIZE).
Introduce a new API: clear_flush_young_ptes() to facilitate batched checking of the young flags and flushing TLB entries, thereby improving performance during large folio reclamation.
Performance testing:
Allocate 10G clean file-backed folios by mmap() in a memory cgroup, and try to reclaim 8G file-backed folios via the memory.reclaim interface. I can observe 33% performance improvement on my Arm64 32-core server (and 10%+ improvement on my X86 machine). Meanwhile, the hotspot folio_check_references() dropped from approximately 35% to around 5%.
W/o patchset:
real 0m1.518s
user 0m0.000s
sys 0m1.518s
W/ patchset:
real 0m1.018s
user 0m0.000s
sys 0m1.018s
--- arch/arm64/include/asm/pgtable.h | 11 +++++++++++ include/linux/mmu_notifier.h | 9 +++++---- include/linux/pgtable.h | 19 +++++++++++++++++++ mm/rmap.c | 22 ++++++++++++++++++++-- 4 files changed, 55 insertions(+), 6 deletions(-)
arch/arm64/include/asm/pgtable.h +11 -0
@@ -1869,6 +1869,17 @@ static inline int ptep_clear_flush_young(struct vm_area_struct *vma,return contpte_clear_flush_young_ptes (vma, addr, ptep,CONT_PTES ); } +#define clear_flush_young_ptes clear_flush_young_ptes +static inline int clear_flush_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +unsigned int nr) +{ +if (likely (nr ==1 )) +return __ptep_clear_flush_young (vma, addr, ptep); + +return contpte_clear_flush_young_ptes (vma, addr, ptep, nr); +} +#define wrprotect_ptes wrprotect_ptes static __always_inline void wrprotect_ptes(struct mm_struct *mm,unsigned long addr,pte_t *ptep,unsigned int nr)
include/linux/mmu_notifier.h +5 -4
@@ -515,16 +515,17 @@ static inline void mmu_notifier_range_init_owner( range->owner = owner; } -#define ptep_clear_flush_young_notify (__vma, __address, __ptep) \ +#define ptep_clear_flush_young_notify (__vma, __address, __ptep, __nr) \ ({ \ int __young; \ struct vm_area_struct *___vma = __vma; \ unsigned long ___address = __address; \ - __young = ptep_clear_flush_young(___vma, ___address, __ptep); \ + unsigned int ___nr = __nr; \ + __young = clear_flush_young_ptes(___vma, ___address, __ptep, ___nr); \ __young |= mmu_notifier_clear_flush_young(___vma->vm_mm, \ ___address, \ ___address + \ - PAGE_SIZE); \ + nr * PAGE_SIZE); \ __young; \ })
@@ -650,7 +651,7 @@ static inline void mmu_notifier_subscriptions_destroy(struct mm_struct *mm)#define mmu_notifier_range_update_to_read_only (r) false -#define ptep_clear_flush_young_notify ptep_clear_flush_young +#define ptep_clear_flush_young_notify clear_flush_young_ptes#define pmdp_clear_flush_young_notify pmdp_clear_flush_young#define ptep_clear_young_notify ptep_test_and_clear_young#define pmdp_clear_young_notify pmdp_test_and_clear_young
include/linux/pgtable.h +19 -0
@@ -947,6 +947,25 @@ static inline void wrprotect_ptes(struct mm_struct *mm, unsigned long addr, }#endif +#ifndef clear_flush_young_ptes +static inline int clear_flush_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +unsigned int nr) +{ +int young =0 ; + +for (;;) { + young |=ptep_clear_flush_young (vma, addr, ptep); +if (--nr ==0 ) +break ; + ptep++; + addr +=PAGE_SIZE ; + } + +return young; +} +#endif +/* * On some architectures hardware does not set page access bit when accessing * memory page, it is responsibility of software setting this bit. It brings
mm/rmap.c +20 -2
@@ -827,9 +827,11 @@ static bool folio_referenced_one(struct folio *folio,struct folio_referenced_arg *pra = arg;DEFINE_FOLIO_VMA_WALK (pvmw, folio, vma, address,0 );int ptes =0 , referenced =0 ; +unsigned int nr;while (page_vma_mapped_walk (&pvmw)) { address = pvmw.address; + nr =1 ;if (vma->vm_flags &VM_LOCKED ) { ptes++;
@@ -874,9 +876,21 @@ static bool folio_referenced_one(struct folio *folio,if (lru_gen_look_around (&pvmw)) referenced++; }else if (pvmw.pte) { +if (folio_test_large (folio)) { +unsigned long end_addr =pmd_addr_end (address, vma->vm_end); +unsigned int max_nr = (end_addr - address) >>PAGE_SHIFT ; +pte_t pteval =ptep_get (pvmw.pte); + + nr =folio_pte_batch (folio, pvmw.pte, pteval, max_nr); + } + + ptes += nr;if (ptep_clear_flush_young_notify (vma, address, - pvmw.pte)) + pvmw.pte, nr)) referenced++; +/* Skip the batched PTEs */ + pvmw.pte += nr -1 ; + pvmw.address += (nr -1 ) *PAGE_SIZE ; }else if (IS_ENABLED (CONFIG_TRANSPARENT_HUGEPAGE )) {if (pmdp_clear_flush_young_notify (vma, address, pvmw.pmd))
@@ -886,7 +900,11 @@ static bool folio_referenced_one(struct folio *folio,WARN_ON_ONCE (1 ); } - pra->mapcount--; + pra->mapcount -= nr; +if (ptes == pvmw.nr_pages) { +page_vma_mapped_walk_done (&pvmw); +break ; + } }if (referenced)
Signature · 1 line
2.47.3
Hi Baolin,
Quoted 6 lines · Baolin Wang
Currently,
folio_referenced_one()always checks the young flag for each PTE sequentially, which is inefficient for large folios. This inefficiency is especially noticeable when reclaiming clean file-backed large folios, wherefolio_referenced()is observed as a significant performance hotspot.
Quoted 7 lines
Moreover, on Arm architecture, which supports contiguous PTEs, there is already an optimization to clear the young flags for PTEs within a contiguous range. However, this is not sufficient. We can extend this to perform batched operations for the entire large folio (which might exceed the contiguous range:
CONT_PTE_SIZE).By supporting batched checking of the young flags and flushing TLB entries, I observed a 33% performance improvement in my file-backed folios reclaim tests.
nice!
Quoted 3 lines
BTW, I still noticed a hotspot in
try_to_unmap()in my test. Hope Barry can resend the optimization patch fortry_to_unmap()[1].
Thanks for waking me up. Yes, it's still on my list—I've just had a lot of non-technical issues come up that seriously slowed my progress. Sorry for the delay.
And I suppose we also need that for try_to_migrate().
Quoted 7 lines
[1] https://lore.kernel.org/all/[email protected]/
Baolin Wang (2):
arm64: mm: support batch clearing of the young flag for large folios
mm: rmap: support batched checks of the references for large folios
Quoted 7 lines
arch/arm64/include/asm/pgtable.h | 23 ++++++++++++----- arch/arm64/mm/contpte.c | 44 ++++++++++++++++++++++---------- include/linux/mmu_notifier.h | 9 ++++--- include/linux/pgtable.h | 19 ++++++++++++++ mm/rmap.c | 22 ++++++++++++++-- 5 files changed, 92 insertions(+), 25 deletions(-)
Thanks
Barry
Quoted 22 lines · Barry Song
Hi Baolin,
Quoted 6 lines · Baolin Wang
Currently,
folio_referenced_one()always checks the young flag for each PTE sequentially, which is inefficient for large folios. This inefficiency is especially noticeable when reclaiming clean file-backed large folios, wherefolio_referenced()is observed as a significant performance hotspot.Quoted 7 lines
Moreover, on Arm architecture, which supports contiguous PTEs, there is already an optimization to clear the young flags for PTEs within a contiguous range. However, this is not sufficient. We can extend this to perform batched operations for the entire large folio (which might exceed the contiguous range:
CONT_PTE_SIZE).By supporting batched checking of the young flags and flushing TLB entries, I observed a 33% performance improvement in my file-backed folios reclaim tests.
nice!
Quoted 1 line
Quoted 11 lines
Quoted 2 lines
BTW, I still noticed a hotspot in
try_to_unmap()in my test. Hope Barry can resend the optimization patch fortry_to_unmap()[1].Thanks for waking me up. Yes, it's still on my list—I've just had a lot of non-technical issues come up that seriously slowed my progress. Sorry for the delay.
And I suppose we also need that for
try_to_migrate().Quoted 2 lines
Hi Barry, Baolin.
About the try_to_unmap part, I also noticed that patch and the comment issue "We only support batched swap_duplicate() for unmapping" in that patch. I guess one reason is add_swap_count_continuation right? That limitation will be killed by swap table phase 3:
It can be previewed here:
https://lore.kernel.org/linux-mm/[email protected]/
And I think we will be able to handle that much easier by then. Sorry that it is taking a while to land upstream though.
David Hildenbrand (Red Hat)
maintainerQuoted 5 lines · Baolin Wang
Currently,
folio_referenced_one()always checks the young flag for each PTE sequentially, which is inefficient for large folios. This inefficiency is especially noticeable when reclaiming clean file-backed large folios, wherefolio_referenced()is observed as a significant performance hotspot.
Quoted 7 lines
Moreover, on Arm architecture, which supports contiguous PTEs, there is already an optimization to clear the young flags for PTEs within a contiguous range. However, this is not sufficient. We can extend this to perform batched operations for the entire large folio (which might exceed the contiguous range:
CONT_PTE_SIZE).By supporting batched checking of the young flags and flushing TLB entries, I observed a 33% performance improvement in my file-backed folios reclaim tests.
Can you point at the benchmark or briefly explain what it does? What exactly are we measuring that improves by 33%?
Signature · 3 lines
Cheers David
Quoted 11 lines · David Hildenbrand (Red Hat)
Quoted 10 lines · Baolin Wang
Currently,
folio_referenced_one()always checks the young flag for each PTE sequentially, which is inefficient for large folios. This inefficiency is especially noticeable when reclaiming clean file-backed large folios, wherefolio_referenced()is observed as a significant performance hotspot.Moreover, on Arm architecture, which supports contiguous PTEs, there is already an optimization to clear the young flags for PTEs within a contiguous range. However, this is not sufficient. We can extend this to perform batched operations for the entire large folio (which might exceed the contiguous range:
CONT_PTE_SIZE).
Quoted 4 lines
Quoted 2 lines
By supporting batched checking of the young flags and flushing TLB entries, I observed a 33% performance improvement in my file-backed folios reclaim tests.
Can you point at the benchmark or briefly explain what it does? What exactly are we measuring that improves by 33%?
Sorry for not being clear. I've described the performance test in patch 2, and I should have copied it to the cover letter:
"
Performance testing:
Allocate 10G clean file-backed folios by mmap() in a memory cgroup, and try to reclaim 8G file-backed folios via the memory.reclaim interface. I can observe 33% performance improvement on my Arm64 32-core server (and 10%+ improvement on my X86 machine). Meanwhile, the hotspot folio_check_references() dropped from approximately 35% to around 5%.
W/o patchset:
real 0m1.518s
user 0m0.000s
sys 0m1.518s
W/ patchset:
real 0m1.018s
user 0m0.000s
sys 0m1.018s
"