support batch checking of references and unmapping for large folios
Subsystems (4)
v4 cover support batch checking of references and unmapping 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).
Similar to folio_referenced_one(), we can also apply batched unmapping for large file folios to optimize the performance of file folio reclamation. By supporting batched checking of the young flags, flushing TLB entries, and unmapping, I can observed a significant performance improvements in my performance tests for file folios reclamation. Please check the performance data in the commit message of each patch.
Run stress-ng and mm selftests, no issues were found.
Patch 1: Add a new generic batched PTE helper that supports batched checks of the references for large folios.
Patch 2 - 3: Preparation patches.
patch 4: Implement the Arm64 arch-specific clear_flush_young_ptes(). Patch 5: Support batched unmapping for file large folios.
Changes from v3:
- Fix using an incorrect parameter in ptep_clear_flush_young_notify() (per Liam).
Changes from v2:
- Rearrange the patch set (per Ryan).
- Add pte_cont() check in clear_flush_young_ptes() (per Ryan).
- Add a helper to do contpte block alignment (per Ryan).
- Fix some coding style issues (per Lorenzo and Ryan).
- Add more comments and update the commit message (per Lorenzo and Ryan).
- Add acked tag from Barry. Thanks.
Changes from v1:
- Add a new patch to support batched unmapping for file large folios.
- Update the cover letter
Baolin Wang (5):
mm: rmap: support batched checks of the references for large folios
arm64: mm: factor out the address and ptep alignment into a new helper
arm64: mm: support batch clearing of the young flag for large folios
arm64: mm: implement the architecture-specific
clear_flush_young_ptes()
mm: rmap: support batched unmapping for file large folios
arch/arm64/include/asm/pgtable.h | 23 ++++++++---- arch/arm64/mm/contpte.c | 62 ++++++++++++++++++++------------ include/linux/mmu_notifier.h | 9 ++--- include/linux/pgtable.h | 35 ++++++++++++++++++ mm/rmap.c | 36 ++++++++++++++++--- 5 files changed, 128 insertions(+), 37 deletions(-)
Signature · 1 line
2.47.3
PATCH v4 1/5 mm: rmap: 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 Arm64 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. And it will be overridden by the architecture that implements a more efficient batch operation in the following patches.
--- include/linux/mmu_notifier.h | 9 +++++---- include/linux/pgtable.h | 35 +++++++++++++++++++++++++++++++++++ mm/rmap.c | 29 +++++++++++++++++++++++++++-- 3 files changed, 67 insertions(+), 6 deletions(-)
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 +35 -0
@@ -1087,6 +1087,41 @@ static inline void wrprotect_ptes(struct mm_struct *mm, unsigned long addr, }#endif +#ifndef clear_flush_young_ptes +/** +* clear_flush_young_ptes - Clear the access bit and perform a TLB flush for PTEs +* that map consecutive pages of the same folio. +* @vma: The virtual memory area the pages are mapped into. +* @addr: Address the first page is mapped at. +* @ptep: Page table pointer for the first entry. +* @nr: Number of entries to clear access bit. +* +* May be overridden by the architecture; otherwise, implemented as a simple +* loop over ptep_clear_flush_young(). +* +* Note that PTE bits in the PTE range besides the PFN can differ. For example, +* some PTEs might be write-protected. +* +* Context: The caller holds the page table lock. The PTEs map consecutive +* pages that belong to the same folio. The PTEs are all in the same PMD. +*/ +static inline int clear_flush_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +unsigned int nr) +{ +int young; + + young =ptep_clear_flush_young (vma, addr, ptep); +while (--nr) { + ptep++; + addr +=PAGE_SIZE ; + young |=ptep_clear_flush_young (vma, addr, ptep); + } + +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 +27 -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,24 @@ 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 +903,15 @@ static bool folio_referenced_one(struct folio *folio,WARN_ON_ONCE (1 ); } - pra->mapcount--; + pra->mapcount -= nr; +/* +* If we are sure that we batched the entire folio, +* we can just optimize and stop right here. +*/ +if (ptes == pvmw.nr_pages) { +page_vma_mapped_walk_done (&pvmw); +break ; + } }if (referenced)
Signature · 1 line
2.47.3
Re: [PATCH v4 1/5] mm: rmap: support batched checks of the references for large folios
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 Arm64 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 6 lines
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. And it will be overridden by the architecture that implements a more efficient batch operation in the following patches.Signed-off-by Baolin Wang <[email protected]>
With the 2 niggles below addressed:
Quoted 11 lines
--- include/linux/mmu_notifier.h | 9 +++++---- include/linux/pgtable.h | 35 +++++++++++++++++++++++++++++++++++ mm/rmap.c | 29 +++++++++++++++++++++++++++-- 3 files changed, 67 insertions(+), 6 deletions(-)include/linux/mmu_notifier.h +0
-0
include/linux/mmu_notifier.h static inline void mmu_notifier_range_init_owner( · line 518
Quoted 5 lines
include/linux/mmu_notifier.h +1
-1@@ -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) \
I think I previously suggested that this should be renamed to clear_flush_young_ptes_notify() given that it is now a batch operation. Were others against that or did you forget?
Quoted 52 lines
include/linux/mmu_notifier.h +4
-3({ \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_younginclude/linux/pgtable.h +20
-0@@ -1087,6 +1087,41 @@ static inline void wrprotect_ptes(struct mm_struct *mm, unsigned long addr, }#endif +#ifndef clear_flush_young_ptes +/** +* clear_flush_young_ptes - Clear the access bit and perform a TLB flush for PTEs +* that map consecutive pages of the same folio. +* @vma: The virtual memory area the pages are mapped into. +* @addr: Address the first page is mapped at. +* @ptep: Page table pointer for the first entry. +* @nr: Number of entries to clear access bit. +* +* May be overridden by the architecture; otherwise, implemented as a simple +* loop over ptep_clear_flush_young(). +* +* Note that PTE bits in the PTE range besides the PFN can differ. For example, +* some PTEs might be write-protected. +* +* Context: The caller holds the page table lock. The PTEs map consecutive +* pages that belong to the same folio. The PTEs are all in the same PMD. +*/ +static inline int clear_flush_young_ptes(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep,
include/linux/pgtable.h static inline void wrprotect_ptes(struct mm_struct *mm, unsigned long addr, · line 1119
Quoted 10 lines
include/linux/pgtable.h +10
-0+ unsigned int nr) +{ +int young; + + young =ptep_clear_flush_young (vma, addr, ptep); +while (--nr) { + ptep++; + addr +=PAGE_SIZE ; + young |=ptep_clear_flush_young (vma, addr, ptep); + }
I think it's better to avoid the two ptep_clear_flush_young() calls if we can. Personally I think we should just go for the simple:
for (i = 0; i < nr; ++i, ++ptep, addr += PAGE_SIZE) young |= ptep_clear_flush_young(vma, addr, ptep);
Quoted 67 lines
include/linux/pgtable.h +5
-0+ + 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 +27
-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,24 @@ 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 +903,15 @@ static bool folio_referenced_one(struct folio *folio,WARN_ON_ONCE (1 ); } - pra->mapcount--; + pra->mapcount -= nr; +/* +* If we are sure that we batched the entire folio, +* we can just optimize and stop right here. +*/ +if (ptes == pvmw.nr_pages) { +page_vma_mapped_walk_done (&pvmw); +break ; + } }if (referenced)
Re: [PATCH v4 1/5] mm: rmap: support batched checks of the references for large folios
Hi Ryan,
Quoted 16 lines · Ryan Roberts
Quoted 15 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 Arm64 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. And it will be overridden by the architecture that implements a more efficient batch operation in the following patches.
Quoted 5 lines
Quoted 1 line
Signed-off-by Baolin Wang <[email protected]>With the 2 niggles below addressed:
Reviewed-by Ryan Roberts <[email protected]>
Thanks.
Quoted 14 lines
Quoted 14 lines
--- include/linux/mmu_notifier.h | 9 +++++---- include/linux/pgtable.h | 35 +++++++++++++++++++++++++++++++++++ mm/rmap.c | 29 +++++++++++++++++++++++++++-- 3 files changed, 67 insertions(+), 6 deletions(-)include/linux/mmu_notifier.h +0
-0@@ -515,16 +515,17 @@ static inline void mmu_notifier_range_init_owner( range->owner = owner; }
Quoted 6 lines
include/linux/mmu_notifier.h static inline void mmu_notifier_range_init_owner( · line 518
Quoted 2 lines
include/linux/mmu_notifier.h +1
-1-#define ptep_clear_flush_young_notify (__vma, __address, __ptep) \ +#define ptep_clear_flush_young_notify (__vma, __address, __ptep, __nr) \I think I previously suggested that this should be renamed to
clear_flush_young_ptes_notify()given that it is now a batch operation. Were others against that or did you forget?
Sorry, I missed this comment. Yes, sounds reasonable to me. Will do in the next version.
Quoted 66 lines
Quoted 52 lines
include/linux/mmu_notifier.h +4
-3({ \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_younginclude/linux/pgtable.h +20
-0@@ -1087,6 +1087,41 @@ static inline void wrprotect_ptes(struct mm_struct *mm, unsigned long addr, }#endif +#ifndef clear_flush_young_ptes +/** +* clear_flush_young_ptes - Clear the access bit and perform a TLB flush for PTEs +* that map consecutive pages of the same folio. +* @vma: The virtual memory area the pages are mapped into. +* @addr: Address the first page is mapped at. +* @ptep: Page table pointer for the first entry. +* @nr: Number of entries to clear access bit. +* +* May be overridden by the architecture; otherwise, implemented as a simple +* loop over ptep_clear_flush_young(). +* +* Note that PTE bits in the PTE range besides the PFN can differ. For example, +* some PTEs might be write-protected. +* +* Context: The caller holds the page table lock. The PTEs map consecutive +* pages that belong to the same folio. The PTEs are all in the same PMD. +*/ +static inline int clear_flush_young_ptes(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep,include/linux/pgtable.h static inline void wrprotect_ptes(struct mm_struct *mm, unsigned long addr, · line 1119
Quoted 10 lines
include/linux/pgtable.h +10
-0+ unsigned int nr) +{ +int young; + + young =ptep_clear_flush_young (vma, addr, ptep); +while (--nr) { + ptep++; + addr +=PAGE_SIZE ; + young |=ptep_clear_flush_young (vma, addr, ptep); + }I think it's better to avoid the two
ptep_clear_flush_young()calls if we can. Personally I think we should just go for the simple:
Quoted 2 lines
for (i = 0; i < nr; ++i, ++ptep, addr += PAGE_SIZE) young |= ptep_clear_flush_young(vma, addr, ptep);
ACK. Thanks for reviewing.
PATCH v4 2/5 arm64: mm: factor out the address and ptep alignment into a new helper
In reply to Baolin WangFactor out the contpte block's address and ptep alignment into a new helper, and will be reused in the following patch.
No functional changes.
--- arch/arm64/mm/contpte.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-)
arch/arm64/mm/contpte.c +21 -8
@@ -26,6 +26,26 @@ static inline pte_t *contpte_align_down(pte_t *ptep)return PTR_ALIGN_DOWN (ptep,sizeof (*ptep) *CONT_PTES ); } +static inline pte_t *contpte_align_addr_ptep (unsigned long *start, +unsigned long *end,pte_t *ptep, +unsigned int nr) +{ +/* +* Note: caller must ensure these nr PTEs are consecutive (present) +* PTEs that map consecutive pages of the same large folio within a +* single VMA and a single page table. +*/ +if (pte_cont (__ptep_get (ptep + nr -1 ))) + *end =ALIGN (*end,CONT_PTE_SIZE ); + +if (pte_cont (__ptep_get (ptep))) { + *start =ALIGN_DOWN (*start,CONT_PTE_SIZE ); + ptep =contpte_align_down (ptep); + } + +return ptep; +} +static void contpte_try_unfold_partial (struct mm_struct *mm,unsigned long addr,pte_t *ptep,unsigned int nr) {
@@ -569,14 +589,7 @@ void contpte_clear_young_dirty_ptes(struct vm_area_struct *vma,unsigned long start = addr;unsigned long end = start + nr *PAGE_SIZE ; -if (pte_cont (__ptep_get (ptep + nr -1 ))) - end =ALIGN (end,CONT_PTE_SIZE ); - -if (pte_cont (__ptep_get (ptep))) { - start =ALIGN_DOWN (start,CONT_PTE_SIZE ); - ptep =contpte_align_down (ptep); - } - + ptep =contpte_align_addr_ptep (&start, &end, ptep, nr);__clear_young_dirty_ptes (vma, start, ptep, (end - start) /PAGE_SIZE , flags); }EXPORT_SYMBOL_GPL (contpte_clear_young_dirty_ptes);
Signature · 1 line
2.47.3
Re: [PATCH v4 2/5] arm64: mm: factor out the address and ptep alignment into a new helper
Quoted 6 lines · Baolin Wang
Factor out the contpte block's address and ptep alignment into a new helper, and will be reused in the following patch.
No functional changes.
Signed-off-by Baolin Wang <[email protected]>
LGTM:
Quoted 51 lines
--- arch/arm64/mm/contpte.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-)arch/arm64/mm/contpte.c +21
-8@@ -26,6 +26,26 @@ static inline pte_t *contpte_align_down(pte_t *ptep)return PTR_ALIGN_DOWN (ptep,sizeof (*ptep) *CONT_PTES ); } +static inline pte_t *contpte_align_addr_ptep (unsigned long *start, +unsigned long *end,pte_t *ptep, +unsigned int nr) +{ +/* +* Note: caller must ensure these nr PTEs are consecutive (present) +* PTEs that map consecutive pages of the same large folio within a +* single VMA and a single page table. +*/ +if (pte_cont (__ptep_get (ptep + nr -1 ))) + *end =ALIGN (*end,CONT_PTE_SIZE ); + +if (pte_cont (__ptep_get (ptep))) { + *start =ALIGN_DOWN (*start,CONT_PTE_SIZE ); + ptep =contpte_align_down (ptep); + } + +return ptep; +} +static void contpte_try_unfold_partial (struct mm_struct *mm,unsigned long addr,pte_t *ptep,unsigned int nr) {@@ -569,14 +589,7 @@ void contpte_clear_young_dirty_ptes(struct vm_area_struct *vma,unsigned long start = addr;unsigned long end = start + nr *PAGE_SIZE ; -if (pte_cont (__ptep_get (ptep + nr -1 ))) - end =ALIGN (end,CONT_PTE_SIZE ); - -if (pte_cont (__ptep_get (ptep))) { - start =ALIGN_DOWN (start,CONT_PTE_SIZE ); - ptep =contpte_align_down (ptep); - } - + ptep =contpte_align_addr_ptep (&start, &end, ptep, nr);__clear_young_dirty_ptes (vma, start, ptep, (end - start) /PAGE_SIZE , flags); }EXPORT_SYMBOL_GPL (contpte_clear_young_dirty_ptes);
PATCH v4 3/5 arm64: mm: support batch clearing of the young flag for large folios
In reply to Baolin WangCurrently, 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 that map consecutive pages of the same large folio in a single VMA and a single page table.
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 | 33 ++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 20 deletions(-)
arch/arm64/include/asm/pgtable.h +6 -6
@@ -1648,10 +1648,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); +int contpte_test_and_clear_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep,unsigned int nr); +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,
@@ -1823,7 +1823,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
@@ -1835,7 +1835,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 +19 -14
@@ -508,8 +508,9 @@ pte_t contpte_get_and_clear_full_ptes(struct mm_struct *mm, }EXPORT_SYMBOL_GPL (contpte_get_and_clear_full_ptes); -int contpte_ptep_test_and_clear_young (struct vm_area_struct *vma, -unsigned long addr,pte_t *ptep) +int contpte_test_and_clear_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +unsigned int nr) {/* * ptep_clear_flush_young() technically requires us to clear the access
@@ -518,41 +519,45 @@ int contpte_ptep_test_and_clear_young(struct vm_area_struct *vma,* contig range when the range is covered by a single folio, we can get * away with clearing young for the whole contig range here, so we avoid * having to unfold. +* +* The 'nr' means consecutive (present) PTEs that map consecutive pages +* of the same large folio in a single VMA and a single page table. */ +unsigned long end = addr + nr *PAGE_SIZE ;int young =0 ; -int i; - ptep =contpte_align_down (ptep); - addr =ALIGN_DOWN (addr,CONT_PTE_SIZE ); - -for (i =0 ; i <CONT_PTES ; i++, ptep++, addr +=PAGE_SIZE ) + ptep =contpte_align_addr_ptep (&addr, &end, ptep, nr); +for (; addr != end; ptep++, addr +=PAGE_SIZE ) young |=__ptep_test_and_clear_young (vma, addr, ptep);return young; } -EXPORT_SYMBOL_GPL (contpte_ptep_test_and_clear_young); +EXPORT_SYMBOL_GPL (contpte_test_and_clear_young_ptes); -int contpte_ptep_clear_flush_young (struct vm_area_struct *vma, -unsigned long addr,pte_t *ptep) +int contpte_clear_flush_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +unsigned int nr) {int young; - young =contpte_ptep_test_and_clear_young (vma, addr, ptep); + young =contpte_test_and_clear_young_ptes (vma, addr, ptep, nr);if (young) { +unsigned long end = addr + nr *PAGE_SIZE ; + +contpte_align_addr_ptep (&addr, &end, ptep, nr);/* * See comment in __ptep_clear_flush_young(); same rationale for * eliding the trailing DSB applies here. */ - addr =ALIGN_DOWN (addr,CONT_PTE_SIZE ); -__flush_tlb_range_nosync (vma->vm_mm, addr, addr +CONT_PTE_SIZE , +__flush_tlb_range_nosync (vma->vm_mm, addr, end,PAGE_SIZE , true,3 ); }return young; } -EXPORT_SYMBOL_GPL (contpte_ptep_clear_flush_young); +EXPORT_SYMBOL_GPL (contpte_clear_flush_young_ptes);void contpte_wrprotect_ptes (struct mm_struct *mm,unsigned long addr,pte_t *ptep,unsigned int nr)
Signature · 1 line
2.47.3
PATCH 1/1 arm64: mm: fix passing the incorrect 'CONT_PTES' for non-batched APIs
Since contpte_test_and_clear_young_ptes() and contpte_clear_flush_young_ptes have already performed CONT_PTE_SIZE alignment and will clear the young flag for the entire cont block, Their non-batched callers do not need to pass in 'CONT_PTES' to specify the cont block range. Otherwise, it may exceed the range of a single cont block for the non-batched cases.
---
Hi Andrew,
As I conducted more tests, I found that the ptep_test_and_clear_young() operation may clear the young flag beyond a single cont block range, causing issues. Please fold this fixup into this patch to solve this issue. Thanks.
--- arch/arm64/include/asm/pgtable.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
arch/arm64/include/asm/pgtable.h +2 -2
@@ -1823,7 +1823,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_test_and_clear_young_ptes (vma, addr, ptep,CONT_PTES ); +return contpte_test_and_clear_young_ptes (vma, addr, ptep,1 ); }#define __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH
@@ -1835,7 +1835,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_clear_flush_young_ptes (vma, addr, ptep,CONT_PTES ); +return contpte_clear_flush_young_ptes (vma, addr, ptep,1 ); }#define wrprotect_ptes wrprotect_ptes
Signature · 1 line
2.47.3
Re: [PATCH v4 3/5] arm64: mm: support batch clearing of the young flag for large folios
In reply to Baolin WangQuoted 35 lines · Baolin Wang
Currently,
contpte_ptep_test_and_clear_young()andcontpte_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 that map consecutive pages of the same large folio in a single VMA and a single page table.While we are at it, rename the functions to maintain consistency with other contpte_*() functions.
Signed-off-by Baolin Wang <[email protected]>--- arch/arm64/include/asm/pgtable.h | 12 ++++++------ arch/arm64/mm/contpte.c | 33 ++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 20 deletions(-)arch/arm64/include/asm/pgtable.h +4
-4@@ -1648,10 +1648,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); +int contpte_test_and_clear_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep,unsigned int nr); +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,
arch/arm64/include/asm/pgtable.h static inline int ptep_test_and_clear_young(struct vm_area_struct *vma, · line 1826
Quoted 5 lines
arch/arm64/include/asm/pgtable.h +1
-1@@ -1823,7 +1823,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 );
As per your fixup patch, I agree that nr should be 1 here, not CONT_PTES.
arch/arm64/include/asm/pgtable.h static inline int ptep_clear_flush_young(struct vm_area_struct *vma, · line 1838
Quoted 9 lines
arch/arm64/include/asm/pgtable.h +1
-1}#define __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH@@ -1835,7 +1835,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 );
And same here.
Quoted 58 lines
arch/arm64/include/asm/pgtable.h +0
-0}#define wrprotect_ptes wrprotect_ptesarch/arm64/mm/contpte.c +15
-11@@ -508,8 +508,9 @@ pte_t contpte_get_and_clear_full_ptes(struct mm_struct *mm, }EXPORT_SYMBOL_GPL (contpte_get_and_clear_full_ptes); -int contpte_ptep_test_and_clear_young (struct vm_area_struct *vma, -unsigned long addr,pte_t *ptep) +int contpte_test_and_clear_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +unsigned int nr) {/* * ptep_clear_flush_young() technically requires us to clear the access @@ -518,41 +519,45 @@ int contpte_ptep_test_and_clear_young(struct vm_area_struct *vma,* contig range when the range is covered by a single folio, we can get * away with clearing young for the whole contig range here, so we avoid * having to unfold. +* +* The 'nr' means consecutive (present) PTEs that map consecutive pages +* of the same large folio in a single VMA and a single page table. */ +unsigned long end = addr + nr *PAGE_SIZE ;int young =0 ; -int i; - ptep =contpte_align_down (ptep); - addr =ALIGN_DOWN (addr,CONT_PTE_SIZE ); - -for (i =0 ; i <CONT_PTES ; i++, ptep++, addr +=PAGE_SIZE ) + ptep =contpte_align_addr_ptep (&addr, &end, ptep, nr); +for (; addr != end; ptep++, addr +=PAGE_SIZE ) young |=__ptep_test_and_clear_young (vma, addr, ptep);return young; } -EXPORT_SYMBOL_GPL (contpte_ptep_test_and_clear_young); +EXPORT_SYMBOL_GPL (contpte_test_and_clear_young_ptes); -int contpte_ptep_clear_flush_young (struct vm_area_struct *vma, -unsigned long addr,pte_t *ptep) +int contpte_clear_flush_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +unsigned int nr) {int young; - young =contpte_ptep_test_and_clear_young (vma, addr, ptep); + young =contpte_test_and_clear_young_ptes (vma, addr, ptep, nr);if (young) { +unsigned long end = addr + nr *PAGE_SIZE ;
arch/arm64/mm/contpte.c int contpte_ptep_test_and_clear_young(struct vm_area_struct *vma, · line 555
Quoted 10 lines
arch/arm64/mm/contpte.c +3
-2+ +contpte_align_addr_ptep (&addr, &end, ptep, nr);/* * See comment in __ptep_clear_flush_young(); same rationale for * eliding the trailing DSB applies here. */ - addr =ALIGN_DOWN (addr,CONT_PTE_SIZE ); -__flush_tlb_range_nosync (vma->vm_mm, addr, addr +CONT_PTE_SIZE , +__flush_tlb_range_nosync (vma->vm_mm, addr, end,PAGE_SIZE , true,3 );
Hmm... The requirement is that we must flush the _page_ if clearing access for a pte that does not have the contiguous bit set, or we must flush the _contpte block_ if clearing access for a pte that does have the contiguous bit set.
With your changes, you may call for a large range that covers multiple contpte blocks but only has a single pte in a single contpte block for which the access bit was previously set. But that will cause flushing the TLB for the full range. Could this cause a performance issue? Yes, no, maybe... I think it's unlikely but I wouldn't rule it out in some edge case.
I wonder if it's better to track the sub-ranges where access was cleared and only issue tlbi for those sub-ranges? Probably just keep it simple (the way you have done it) until/unless we see an actual problem?
Thanks,
Ryan
Quoted 9 lines
arch/arm64/mm/contpte.c +1
-1}return young; } -EXPORT_SYMBOL_GPL (contpte_ptep_clear_flush_young); +EXPORT_SYMBOL_GPL (contpte_clear_flush_young_ptes);void contpte_wrprotect_ptes (struct mm_struct *mm,unsigned long addr,pte_t *ptep,unsigned int nr)
Re: [PATCH v4 3/5] arm64: mm: support batch clearing of the young flag for large folios
Quoted 39 lines · Ryan Roberts
Quoted 38 lines · Baolin Wang
Currently,
contpte_ptep_test_and_clear_young()andcontpte_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 that map consecutive pages of the same large folio in a single VMA and a single page table.While we are at it, rename the functions to maintain consistency with other contpte_*() functions.
Signed-off-by Baolin Wang <[email protected]>--- arch/arm64/include/asm/pgtable.h | 12 ++++++------ arch/arm64/mm/contpte.c | 33 ++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 20 deletions(-)arch/arm64/include/asm/pgtable.h +4
-4@@ -1648,10 +1648,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); +int contpte_test_and_clear_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep,unsigned int nr); +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,@@ -1823,7 +1823,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);
Quoted 4 lines
arch/arm64/include/asm/pgtable.h static inline int ptep_test_and_clear_young(struct vm_area_struct *vma, · line 1826
Quoted 2 lines
arch/arm64/include/asm/pgtable.h +1
-1-return contpte_ptep_test_and_clear_young (vma, addr, ptep); +return contpte_test_and_clear_young_ptes (vma, addr, ptep,CONT_PTES );As per your fixup patch, I agree that nr should be 1 here, not
CONT_PTES.
Yes.
Quoted 91 lines
arch/arm64/include/asm/pgtable.h static inline int ptep_clear_flush_young(struct vm_area_struct *vma, · line 1838
Quoted 9 lines
arch/arm64/include/asm/pgtable.h +1
-1}#define __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH@@ -1835,7 +1835,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 );And same here.
Quoted 58 lines
arch/arm64/include/asm/pgtable.h +0
-0}#define wrprotect_ptes wrprotect_ptesarch/arm64/mm/contpte.c +15
-11@@ -508,8 +508,9 @@ pte_t contpte_get_and_clear_full_ptes(struct mm_struct *mm, }EXPORT_SYMBOL_GPL (contpte_get_and_clear_full_ptes); -int contpte_ptep_test_and_clear_young (struct vm_area_struct *vma, -unsigned long addr,pte_t *ptep) +int contpte_test_and_clear_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +unsigned int nr) {/* * ptep_clear_flush_young() technically requires us to clear the access @@ -518,41 +519,45 @@ int contpte_ptep_test_and_clear_young(struct vm_area_struct *vma,* contig range when the range is covered by a single folio, we can get * away with clearing young for the whole contig range here, so we avoid * having to unfold. +* +* The 'nr' means consecutive (present) PTEs that map consecutive pages +* of the same large folio in a single VMA and a single page table. */ +unsigned long end = addr + nr *PAGE_SIZE ;int young =0 ; -int i; - ptep =contpte_align_down (ptep); - addr =ALIGN_DOWN (addr,CONT_PTE_SIZE ); - -for (i =0 ; i <CONT_PTES ; i++, ptep++, addr +=PAGE_SIZE ) + ptep =contpte_align_addr_ptep (&addr, &end, ptep, nr); +for (; addr != end; ptep++, addr +=PAGE_SIZE ) young |=__ptep_test_and_clear_young (vma, addr, ptep);return young; } -EXPORT_SYMBOL_GPL (contpte_ptep_test_and_clear_young); +EXPORT_SYMBOL_GPL (contpte_test_and_clear_young_ptes); -int contpte_ptep_clear_flush_young (struct vm_area_struct *vma, -unsigned long addr,pte_t *ptep) +int contpte_clear_flush_young_ptes (struct vm_area_struct *vma, +unsigned long addr,pte_t *ptep, +unsigned int nr) {int young; - young =contpte_ptep_test_and_clear_young (vma, addr, ptep); + young =contpte_test_and_clear_young_ptes (vma, addr, ptep, nr);if (young) { +unsigned long end = addr + nr *PAGE_SIZE ;arch/arm64/mm/contpte.c int contpte_ptep_test_and_clear_young(struct vm_area_struct *vma, · line 555
Quoted 10 lines
arch/arm64/mm/contpte.c +3
-2+ +contpte_align_addr_ptep (&addr, &end, ptep, nr);/* * See comment in __ptep_clear_flush_young(); same rationale for * eliding the trailing DSB applies here. */ - addr =ALIGN_DOWN (addr,CONT_PTE_SIZE ); -__flush_tlb_range_nosync (vma->vm_mm, addr, addr +CONT_PTE_SIZE , +__flush_tlb_range_nosync (vma->vm_mm, addr, end,PAGE_SIZE , true,3 );Hmm... The requirement is that we must flush the _page_ if clearing access for a pte that does not have the contiguous bit set, or we must flush the
_contpteblock_if clearing access for a pte that does have the contiguous bit set.With your changes, you may call for a large range that covers multiple contpte blocks but only has a single pte in a single contpte block for which the access bit was previously set. But that will cause flushing the TLB for the full range. Could this cause a performance issue? Yes, no, maybe... I think it's unlikely but I wouldn't rule it out in some edge case.
Quoted 3 lines
I wonder if it's better to track the sub-ranges where access was cleared and only issue tlbi for those sub-ranges? Probably just keep it simple (the way you have done it) until/unless we see an actual problem?
Good question. Indeed, as you said, we flush the TLB per folio now, which might increase the flush range. However, I think this approach is relatively reasonable for now.
First, the mm-core also tracks the access status per folio, and it's really unnecessary to add excessive complexity to track the access status of sub-pages (or sub-ranges). I can already imagine that tracking the access status for each cont-block range as well as for non-cont pages across the entire large folio range, which can be too complicated.
Second, __flush_tlb_range_nosync() is a lightweight flush. I quickly ran a measurement on my machine and found that the overhead of __flush_tlb_range_nosync() barely changes between nr=16 and nr=256 (both are around 40 ns).
Therefore, I would still prefer to keep the logic here simple.
Re: [PATCH v4 3/5] arm64: mm: support batch clearing of the young flag for large folios
Quoted 149 lines · Baolin Wang
Quoted 38 lines · Ryan Roberts
Quoted 37 lines · Baolin Wang
Currently,
contpte_ptep_test_and_clear_young()andcontpte_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 that map consecutive
pages of the same large folio in a single VMA and a single page table.While we are at it, rename the functions to maintain consistency with other contpte_*() functions.
Signed-off-by Baolin Wang <[email protected]>---
arch/arm64/include/asm/pgtable.h| 12 ++++++------ arch/arm64/mm/contpte.c | 33 ++++++++++++++++++--------------2 files changed, 25 insertions(+), 20 deletions(-)arch/arm64/include/asm/pgtable.h +4
-4@@ -1648,10 +1648,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); +int contpte_test_and_clear_young_ptes (struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep, unsigned int nr); +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,Quoted 8 lines
arch/arm64/include/asm/pgtable.h static inline int ptep_test_and_clear_young(struct · line 1827
Quoted 6 lines
arch/arm64/include/asm/pgtable.h +1
-0@@ -1823,7 +1823,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);As per your fixup patch, I agree that nr should be 1 here, not
CONT_PTES.Yes.
Quoted 83 lines
arch/arm64/include/asm/pgtable.h static inline int ptep_clear_flush_young(struct · line 1839
Quoted 8 lines
arch/arm64/include/asm/pgtable.h +1
-0} #define __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH@@ -1835,7 +1835,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);And same here.
Quoted 51 lines
arch/arm64/include/asm/pgtable.h +0
-0} #define wrprotect_ptes wrprotect_ptesarch/arm64/mm/contpte.c +14
-7@@ -508,8 +508,9 @@ pte_t contpte_get_and_clear_full_ptes(struct mm_struct *mm, } EXPORT_SYMBOL_GPL(contpte_get_and_clear_full_ptes); -int contpte_ptep_test_and_clear_young(struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +int contpte_test_and_clear_young_ptes(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep, + unsigned int nr) { /** ptep_clear_flush_young() technically requires us to clear the access @@ -518,41 +519,45 @@ int contpte_ptep_test_and_clear_young(structvm_area_struct *vma, * contig range when the range is covered by a single folio, we can get * away with clearing young for the whole contig range here, so we avoid * having to unfold. +* +* The 'nr' means consecutive (present) PTEs that map consecutive pages +* of the same large folio in a single VMA and a single page table. */ + unsigned long end = addr + nr * PAGE_SIZE; int young = 0; - int i; - ptep = contpte_align_down(ptep); - addr = ALIGN_DOWN(addr, CONT_PTE_SIZE); - - for (i = 0; i < CONT_PTES; i++, ptep++, addr += PAGE_SIZE) + ptep = contpte_align_addr_ptep(&addr, &end, ptep, nr); + for (; addr != end; ptep++, addr += PAGE_SIZE) young |= __ptep_test_and_clear_young(vma, addr, ptep); return young; } -EXPORT_SYMBOL_GPL (contpte_ptep_test_and_clear_young); +EXPORT_SYMBOL_GPL (contpte_test_and_clear_young_ptes); -int contpte_ptep_clear_flush_young(struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +int contpte_clear_flush_young_ptes(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep, + unsigned int nr) { int young; - young = contpte_ptep_test_and_clear_young(vma, addr, ptep); + young = contpte_test_and_clear_young_ptes(vma, addr, ptep, nr); if (young) { + unsigned long end = addr + nr * PAGE_SIZE;arch/arm64/mm/contpte.c int contpte_ptep_test_and_clear_young(struct · line 553
Quoted 10 lines
arch/arm64/mm/contpte.c +3
-2+ + contpte_align_addr_ptep(&addr, &end, ptep, nr); /* * See comment in __ptep_clear_flush_young(); same rationale for * eliding the trailing DSB applies here. */ - addr = ALIGN_DOWN(addr, CONT_PTE_SIZE); - __flush_tlb_range_nosync(vma->vm_mm, addr, addr + CONT_PTE_SIZE, + __flush_tlb_range_nosync(vma->vm_mm, addr, end, PAGE_SIZE, true, 3);Hmm... The requirement is that we must flush the _page_ if clearing access for a pte that does not have the contiguous bit set, or we must flush the
_contpteblock_if clearing access for a pte that does have the contiguous bit set.With your changes, you may call for a large range that covers multiple contpte blocks but only has a single pte in a single contpte block for which the access bit was previously set. But that will cause flushing the TLB for the full range. Could this cause a performance issue? Yes, no, maybe... I think it's unlikely but I wouldn't rule it out in some edge case.
Quoted 3 lines
I wonder if it's better to track the sub-ranges where access was cleared and only issue tlbi for those sub-ranges? Probably just keep it simple (the way you have done it) until/unless we see an actual problem?
Good question. Indeed, as you said, we flush the TLB per folio now, which might increase the flush range. However, I think this approach is relatively reasonable for now.
First, the mm-core also tracks the access status per folio, and it's really unnecessary to add excessive complexity to track the access status of sub-pages (or sub-ranges). I can already imagine that tracking the access status for each cont-block range as well as for non-cont pages across the entire large folio range, which can be too complicated.
Quoted 4 lines
Second,
__flush_tlb_range_nosync()is a lightweight flush. I quickly ran a measurement on my machine and found that the overhead of__flush_tlb_range_nosync()barely changes between nr=16 and nr=256 (both are around 40 ns).
I'm not concerned about the direct cost of the flush; I agree it should be lightweight given we elide the trailing DSB. (although there is a possible case on older HW that doesn't support TLBI-by-range where this will be converted to multiple TLBI-by-page instructions and that can cause stalls if there are too many of them).
My concern was the opportunity cost of evicting the entries for all the non-accessed parts of the folio from the TLB. But of course, I'm talking nonsense because the architecture does not allow caching non-accessed entries in the TLB.
So doesn't sound like a problem; I think we can ignore this. Sorry for the noise.
Quoted 2 lines
Therefore, I would still prefer to keep the logic here simple.
Agreed.
Thanks,
Ryan
Re: [PATCH v4 3/5] arm64: mm: support batch clearing of the young flag for large folios
Quoted 161 lines · Ryan Roberts
Quoted 149 lines · Baolin Wang
Quoted 38 lines · Ryan Roberts
Quoted 37 lines · Baolin Wang
Currently,
contpte_ptep_test_and_clear_young()andcontpte_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 that map consecutive
pages of the same large folio in a single VMA and a single page table.While we are at it, rename the functions to maintain consistency with other contpte_*() functions.
Signed-off-by Baolin Wang <[email protected]>--- arch/arm64/include/asm/pgtable.h | 12 ++++++------ arch/arm64/mm/contpte.c | 33 ++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 20 deletions(-)arch/arm64/include/asm/pgtable.h +4
-4@@ -1648,10 +1648,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); +int contpte_test_and_clear_young_ptes (struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep, unsigned int nr); +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,Quoted 8 lines
arch/arm64/include/asm/pgtable.h static inline int ptep_test_and_clear_young(struct · line 1827
Quoted 6 lines
arch/arm64/include/asm/pgtable.h +1
-0@@ -1823,7 +1823,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);As per your fixup patch, I agree that nr should be 1 here, not
CONT_PTES.Yes.
Quoted 83 lines
arch/arm64/include/asm/pgtable.h static inline int ptep_clear_flush_young(struct · line 1839
Quoted 8 lines
arch/arm64/include/asm/pgtable.h +1
-0} #define __HAVE_ARCH_PTEP_CLEAR_YOUNG_FLUSH@@ -1835,7 +1835,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);And same here.
Quoted 51 lines
arch/arm64/include/asm/pgtable.h +0
-0} #define wrprotect_ptes wrprotect_ptesarch/arm64/mm/contpte.c +14
-7@@ -508,8 +508,9 @@ pte_t contpte_get_and_clear_full_ptes(struct mm_struct *mm, } EXPORT_SYMBOL_GPL(contpte_get_and_clear_full_ptes); -int contpte_ptep_test_and_clear_young(struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +int contpte_test_and_clear_young_ptes(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep, + unsigned int nr) { /** ptep_clear_flush_young() technically requires us to clear the access @@ -518,41 +519,45 @@ int contpte_ptep_test_and_clear_young(structvm_area_struct *vma, * contig range when the range is covered by a single folio, we can get * away with clearing young for the whole contig range here, so we avoid * having to unfold. +* +* The 'nr' means consecutive (present) PTEs that map consecutive pages +* of the same large folio in a single VMA and a single page table. */ + unsigned long end = addr + nr * PAGE_SIZE; int young = 0; - int i; - ptep = contpte_align_down(ptep); - addr = ALIGN_DOWN(addr, CONT_PTE_SIZE); - - for (i = 0; i < CONT_PTES; i++, ptep++, addr += PAGE_SIZE) + ptep = contpte_align_addr_ptep(&addr, &end, ptep, nr); + for (; addr != end; ptep++, addr += PAGE_SIZE) young |= __ptep_test_and_clear_young(vma, addr, ptep); return young; } -EXPORT_SYMBOL_GPL (contpte_ptep_test_and_clear_young); +EXPORT_SYMBOL_GPL (contpte_test_and_clear_young_ptes); -int contpte_ptep_clear_flush_young(struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +int contpte_clear_flush_young_ptes(struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep, + unsigned int nr) { int young; - young = contpte_ptep_test_and_clear_young(vma, addr, ptep); + young = contpte_test_and_clear_young_ptes(vma, addr, ptep, nr); if (young) { + unsigned long end = addr + nr * PAGE_SIZE;arch/arm64/mm/contpte.c int contpte_ptep_test_and_clear_young(struct · line 553
Quoted 10 lines
arch/arm64/mm/contpte.c +3
-2+ + contpte_align_addr_ptep(&addr, &end, ptep, nr); /* * See comment in __ptep_clear_flush_young(); same rationale for * eliding the trailing DSB applies here. */ - addr = ALIGN_DOWN(addr, CONT_PTE_SIZE); - __flush_tlb_range_nosync(vma->vm_mm, addr, addr + CONT_PTE_SIZE, + __flush_tlb_range_nosync(vma->vm_mm, addr, end, PAGE_SIZE, true, 3);Hmm... The requirement is that we must flush the _page_ if clearing access for a pte that does not have the contiguous bit set, or we must flush the
_contpteblock_if clearing access for a pte that does have the contiguous bit set.With your changes, you may call for a large range that covers multiple contpte blocks but only has a single pte in a single contpte block for which the access bit was previously set. But that will cause flushing the TLB for the full range. Could this cause a performance issue? Yes, no, maybe... I think it's unlikely but I wouldn't rule it out in some edge case.
Quoted 3 lines
I wonder if it's better to track the sub-ranges where access was cleared and only issue tlbi for those sub-ranges? Probably just keep it simple (the way you have done it) until/unless we see an actual problem?
Good question. Indeed, as you said, we flush the TLB per folio now, which might increase the flush range. However, I think this approach is relatively reasonable for now.
First, the mm-core also tracks the access status per folio, and it's really unnecessary to add excessive complexity to track the access status of sub-pages (or sub-ranges). I can already imagine that tracking the access status for each cont-block range as well as for non-cont pages across the entire large folio range, which can be too complicated.
Quoted 4 lines
Second,
__flush_tlb_range_nosync()is a lightweight flush. I quickly ran a measurement on my machine and found that the overhead of__flush_tlb_range_nosync()barely changes between nr=16 and nr=256 (both are around 40 ns).I'm not concerned about the direct cost of the flush; I agree it should be lightweight given we elide the trailing DSB. (although there is a possible case on older HW that doesn't support TLBI-by-range where this will be converted to multiple TLBI-by-page instructions and that can cause stalls if there are too many of them).
Quoted 4 lines
My concern was the opportunity cost of evicting the entries for all the non-accessed parts of the folio from the TLB. But of course, I'm talking nonsense because the architecture does not allow caching non-accessed entries in the TLB.
Ah, now I understand your concern:). Yes, agree.
Quoted 1 line
So doesn't sound like a problem; I think we can ignore this. Sorry for the noise.
OK. No worries.
Quoted 3 lines
Quoted 1 line
Therefore, I would still prefer to keep the logic here simple.
Agreed.
Thanks for your reviewing and valuable input.
PATCH v4 4/5 arm64: mm: implement the architecture-specific clear_flush_young_ptes()
In reply to Baolin WangImplement the Arm64 architecture-specific clear_flush_young_ptes() to enable batched checking of young flags and TLB flushing, 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 +++++++++++ 1 file changed, 11 insertions(+)
arch/arm64/include/asm/pgtable.h +11 -0
@@ -1838,6 +1838,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 && !pte_cont (__ptep_get (ptep)))) +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)
Signature · 1 line
2.47.3
Re: [PATCH v4 4/5] arm64: mm: implement the architecture-specific clear_flush_young_ptes()
Quoted 16 lines · Baolin Wang
Implement the Arm64 architecture-specific
clear_flush_young_ptes()to enable batched checking of young flags and TLB flushing, improving performance during large folio reclamation.Performance testing:
Allocate 10G clean file-backed folios bymmap()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 hotspotfolio_check_references()dropped from approximately 35% to around 5%.W/o patchset:
real 0m1.518s
user 0m0.000s
sys 0m1.518s
Quoted 6 lines
W/ patchset:
real 0m1.018s
user 0m0.000s
sys 0m1.018sSigned-off-by Baolin Wang <[email protected]>
Quoted 26 lines
--- arch/arm64/include/asm/pgtable.h | 11 +++++++++++ 1 file changed, 11 insertions(+)arch/arm64/include/asm/pgtable.h +11
-0@@ -1838,6 +1838,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 && !pte_cont (__ptep_get (ptep)))) +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)
PATCH v4 5/5 mm: rmap: support batched unmapping for file large folios
In reply to Baolin WangSimilar to folio_referenced_one(), we can apply batched unmapping for file large folios to optimize the performance of file folios reclamation.
Barry previously implemented batched unmapping for lazyfree anonymous large folios[1] and did not further optimize anonymous large folios or file-backed large folios at that stage. As for file-backed large folios, the batched unmapping support is relatively straightforward, as we only need to clear the consecutive (present) PTE entries for file-backed large folios.
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 75% performance improvement on my Arm64 32-core server (and 50%+ improvement on my X86 machine) with this patch.
W/o patch:
real 0m1.018s user 0m0.000s sys 0m1.018s
W/ patch:
real 0m0.249s
user 0m0.000s
sys 0m0.249s
[1] https://lore.kernel.org/all/[email protected]/T/#u
--- mm/rmap.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
mm/rmap.c +4 -3
@@ -1862,9 +1862,10 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, end_addr =pmd_addr_end (addr, vma->vm_end); max_nr = (end_addr - addr) >>PAGE_SHIFT ; -/* We only support lazyfree batching for now ... */ -if (!folio_test_anon (folio) ||folio_test_swapbacked (folio)) +/* We only support lazyfree or file folios batching for now ... */ +if (folio_test_anon (folio) &&folio_test_swapbacked (folio))return 1 ; +if (pte_unused (pte))return 1 ;
@@ -2230,7 +2231,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,* * See Documentation/mm/mmu_notifier.rst */ -dec_mm_counter (mm,mm_counter_file (folio)); +add_mm_counter (mm,mm_counter_file (folio), -nr_pages); } discard:if (unlikely (folio_test_hugetlb (folio))) {
Signature · 1 line
2.47.3
Re: [PATCH v4 5/5] mm: rmap: support batched unmapping for file large folios
Quoted 20 lines · Baolin Wang
Similar to
folio_referenced_one(), we can apply batched unmapping for file large folios to optimize the performance of file folios reclamation.Barry previously implemented batched unmapping for lazyfree anonymous large folios[1] and did not further optimize anonymous large folios or file-backed large folios at that stage. As for file-backed large folios, the batched unmapping support is relatively straightforward, as we only need to clear the consecutive (present) PTE entries for file-backed large folios.
Performance testing:
Allocate 10G clean file-backed folios bymmap()in a memory cgroup, and try to reclaim 8G file-backed folios via the memory.reclaim interface. I can observe 75% performance improvement on my Arm64 32-core server (and 50%+ improvement on my X86 machine) with this patch.W/o patch:
real 0m1.018s user 0m0.000s sys 0m1.018s
Quoted 8 lines
W/ patch:
real 0m0.249s
user 0m0.000s
sys 0m0.249s[1] https://lore.kernel.org/all/[email protected]/T/#u
Acked-by Barry Song <[email protected]>Signed-off-by Baolin Wang <[email protected]>
Quoted 30 lines
--- mm/rmap.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)mm/rmap.c +4
-3@@ -1862,9 +1862,10 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, end_addr =pmd_addr_end (addr, vma->vm_end); max_nr = (end_addr - addr) >>PAGE_SHIFT ; -/* We only support lazyfree batching for now ... */ -if (!folio_test_anon (folio) ||folio_test_swapbacked (folio)) +/* We only support lazyfree or file folios batching for now ... */ +if (folio_test_anon (folio) &&folio_test_swapbacked (folio))return 1 ; +if (pte_unused (pte))return 1 ;@@ -2230,7 +2231,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,* * See Documentation/mm/mmu_notifier.rst */ -dec_mm_counter (mm,mm_counter_file (folio)); +add_mm_counter (mm,mm_counter_file (folio), -nr_pages); } discard:if (unlikely (folio_test_hugetlb (folio))) {