convert read_kcore(), vread() to use iterators
Subsystems (6)
v2 cover convert read_kcore(), vread() to use iterators
While reviewing Baoquan's recent changes to permit vread() access to vm_map_ram regions of vmalloc allocations, Willy pointed out [1] that it would be nice to refactor vread() as a whole, since its only user is read_kcore() and the existing form of vread() necessitates the use of a bounce buffer.
This patch series does exactly that, as well as adjusting how we read the kernel text section to avoid the use of a bounce buffer in this case as well.
This patch series necessarily changes the locking used in vmalloc, however tests indicate that this has very little impact on allocation performance (test results are shown in the relevant patch).
This has been tested against the test case which motivated Baoquan's changes in the first place [2] which continues to function correctly, as do the vmalloc self tests.
[1] https://lore.kernel.org/all/Y8WfDSRkc%[email protected]/
[2] https://lore.kernel.org/all/[email protected]/T/#u
v2:
- Fix ordering of vread_iter() parameters
- Fix nommu vread() -> vread_iter()
v1:
https://lore.kernel.org/all/[email protected]/
Lorenzo Stoakes (4):
fs/proc/kcore: Avoid bounce buffer for ktext data
mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock fs/proc/kcore: convert read_kcore() to read_kcore_iter()
mm: vmalloc: convert vread() to vread_iter()
fs/proc/kcore.c | 84 +++++++------------ include/linux/vmalloc.h | 3 +- mm/nommu.c | 10 +-- mm/vmalloc.c | 178 +++++++++++++++++++++------------------- 4 files changed, 130 insertions(+), 145 deletions(-)
base-commit: 4018ab1f7cec061b8425737328edefebdc0ab832 --
2.39.2
PATCH v2 1/4 fs/proc/kcore: Avoid bounce buffer for ktext data
Commit df04abfd181a ("fs/proc/kcore.c: Add bounce buffer for ktext data") introduced the use of a bounce buffer to retrieve kernel text data for /proc/kcore in order to avoid failures arising from hardened user copies enabled by CONFIG_HARDENED_USERCOPY in check_kernel_text_object().
We can avoid doing this if instead of copy_to_user() we use _copy_to_user() which bypasses the hardening check. This is more efficient than using a bounce buffer and simplifies the code.
We do so as part an overall effort to eliminate bounce buffer usage in the function with an eye to converting it an iterator read.
--- fs/proc/kcore.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-)
fs/proc/kcore.c +5 -12
@@ -541,19 +541,12 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)case KCORE_VMEMMAP :case KCORE_TEXT :/* -* Using bounce buffer to bypass the -* hardened user copy kernel text checks. +* We use _copy_to_user() to bypass usermode hardening +* which would otherwise prevent this operation. */ -if (copy_from_kernel_nofault (buf, (void *)start, tsz)) { -if (clear_user (buffer, tsz)) { - ret = -EFAULT ; - goto out; - } - }else { -if (copy_to_user (buffer, buf, tsz)) { - ret = -EFAULT ; - goto out; - } +if (_copy_to_user (buffer, (char *)start, tsz)) { + ret = -EFAULT ; + goto out; }break ;default :
Signature · 1 line
2.39.2
Re: [PATCH v2 1/4] fs/proc/kcore: Avoid bounce buffer for ktext data
Quoted 36 lines · Lorenzo Stoakes
Commit
df04abfd181a("fs/proc/kcore.c: Add bounce buffer for ktext data") introduced the use of a bounce buffer to retrieve kernel text data for/proc/kcorein order to avoid failures arising from hardened user copies enabled byCONFIG_HARDENED_USERCOPYincheck_kernel_text_object().We can avoid doing this if instead of
copy_to_user()we use_copy_to_user()which bypasses the hardening check. This is more efficient than using a bounce buffer and simplifies the code.We do so as part an overall effort to eliminate bounce buffer usage in the function with an eye to converting it an iterator read.
Signed-off-by Lorenzo Stoakes <[email protected]>--- fs/proc/kcore.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-)fs/proc/kcore.c +2
-8@@ -541,19 +541,12 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)case KCORE_VMEMMAP :case KCORE_TEXT :/* -* Using bounce buffer to bypass the -* hardened user copy kernel text checks. +* We use _copy_to_user() to bypass usermode hardening +* which would otherwise prevent this operation. */ -if (copy_from_kernel_nofault (buf, (void *)start, tsz)) { -if (clear_user (buffer, tsz)) { - ret = -EFAULT ; - goto out; - } - }else {
fs/proc/kcore.c read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) · line 552
Quoted 10 lines
fs/proc/kcore.c +3
-4-if (copy_to_user (buffer, buf, tsz)) { - ret = -EFAULT ; - goto out; - } +if (_copy_to_user (buffer, (char *)start, tsz)) { + ret = -EFAULT ; + goto out; }break ;default :
Looks correct to me. The only difference between copy_to_user() and _copy_to_user() is the check_copy_size() check, that ends up calling check_kernel_text_object().
Signature · 3 lines
Thanks, David / dhildenb
PATCH v2 2/4 mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
In reply to Lorenzo Stoakesvmalloc() is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.
The global vmap_area_lock is held when the red/black tree rooted in vmap_are_root is accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.
Each individual vmap_block->lock is likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.
A subset of test_vmalloc.sh performance results:-
fix_size_alloc_test 0.40%full_fit_alloc_test 2.08%long_busy_list_alloc_test 0.34%random_size_alloc_test -0.25%random_size_align_alloc_test 0.06%
...
all tests cycles 0.2%
This represents a tiny reduction in performance that sits barely above noise.
The reason for making this change is to build a basis for vread() to be usable asynchronously, this eliminating the need for a bounce buffer when copying data to userland in read_kcore() and allowing that to be converted to an iterator form.
--- mm/vmalloc.c | 77 +++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 37 deletions(-)
mm/vmalloc.c +40 -37
@@ -40,6 +40,7 @@#include <linux/uaccess.h> #include <linux/hugetlb.h> #include <linux/sched/mm.h> +#include <linux/rwsem.h> #include <asm/tlbflush.h> #include <asm/shmparam.h>
@@ -725,7 +726,7 @@ EXPORT_SYMBOL(vmalloc_to_pfn);#define DEBUG_AUGMENT_LOWEST_MATCH_CHECK 0 -static DEFINE_SPINLOCK (vmap_area_lock); +static DECLARE_RWSEM (vmap_area_lock);static DEFINE_SPINLOCK (free_vmap_area_lock);/* Export for kexec only */ LIST_HEAD (vmap_area_list);
@@ -1537,9 +1538,9 @@ static void free_vmap_area(struct vmap_area *va)/* * Remove from the busy tree/list. */ -spin_lock (&vmap_area_lock); +down_write (&vmap_area_lock);unlink_va (va, &vmap_area_root); -spin_unlock (&vmap_area_lock); +up_write (&vmap_area_lock);/* * Insert/Merge it back to the free tree/list.
@@ -1627,9 +1628,9 @@ static struct vmap_area *alloc_vmap_area(unsigned long size, va->vm =NULL ; va->flags = va_flags; -spin_lock (&vmap_area_lock); +down_write (&vmap_area_lock);insert_vmap_area (va, &vmap_area_root, &vmap_area_list); -spin_unlock (&vmap_area_lock); +up_write (&vmap_area_lock);BUG_ON (!IS_ALIGNED (va->va_start, align));BUG_ON (va->va_start < vstart);
@@ -1854,9 +1855,9 @@ struct vmap_area *find_vmap_area(unsigned long addr) {struct vmap_area *va; -spin_lock (&vmap_area_lock); +down_read (&vmap_area_lock); va =__find_vmap_area (addr, &vmap_area_root); -spin_unlock (&vmap_area_lock); +up_read (&vmap_area_lock);return va; }
@@ -1865,11 +1866,11 @@ static struct vmap_area *find_unlink_vmap_area(unsigned long addr) {struct vmap_area *va; -spin_lock (&vmap_area_lock); +down_write (&vmap_area_lock); va =__find_vmap_area (addr, &vmap_area_root);if (va)unlink_va (va, &vmap_area_root); -spin_unlock (&vmap_area_lock); +up_write (&vmap_area_lock);return va; }
@@ -1914,7 +1915,7 @@ struct vmap_block_queue { };struct vmap_block { -spinlock_t lock; +struct mutex lock;struct vmap_area *va;unsigned long free, dirty; DECLARE_BITMAP(used_map, VMAP_BBMAP_BITS);
@@ -1991,7 +1992,7 @@ static void *new_vmap_block(unsigned int order, gfp_t gfp_mask) } vaddr =vmap_block_vaddr (va->va_start,0 ); -spin_lock_init (&vb->lock); +mutex_init (&vb->lock); vb->va = va;/* At least something should be left free */ BUG_ON (VMAP_BBMAP_BITS <= (1UL << order));
@@ -2026,9 +2027,9 @@ static void free_vmap_block(struct vmap_block *vb) tmp =xa_erase (&vmap_blocks,addr_to_vb_idx (vb->va->va_start));BUG_ON (tmp != vb); -spin_lock (&vmap_area_lock); +down_write (&vmap_area_lock);unlink_va (vb->va, &vmap_area_root); -spin_unlock (&vmap_area_lock); +up_write (&vmap_area_lock);free_vmap_area_noflush (vb->va);kfree_rcu (vb, rcu_head);
@@ -2047,7 +2048,7 @@ static void purge_fragmented_blocks(int cpu)if (!(vb->free + vb->dirty ==VMAP_BBMAP_BITS && vb->dirty !=VMAP_BBMAP_BITS ))continue ; -spin_lock (&vb->lock); +mutex_lock (&vb->lock);if (vb->free + vb->dirty ==VMAP_BBMAP_BITS && vb->dirty !=VMAP_BBMAP_BITS ) { vb->free =0 ;/* prevent further allocs after releasing lock */ vb->dirty =VMAP_BBMAP_BITS ;/* prevent purging it again */
@@ -2056,10 +2057,10 @@ static void purge_fragmented_blocks(int cpu)spin_lock (&vbq->lock);list_del_rcu (&vb->free_list);spin_unlock (&vbq->lock); -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock);list_add_tail (&vb->purge, &purge); }else -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock); }rcu_read_unlock ();
@@ -2101,9 +2102,9 @@ static void *vb_alloc(unsigned long size, gfp_t gfp_mask)list_for_each_entry_rcu (vb, &vbq->free, free_list) {unsigned long pages_off; -spin_lock (&vb->lock); +mutex_lock (&vb->lock);if (vb->free < (1UL << order)) { -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock);continue ; }
@@ -2117,7 +2118,7 @@ static void *vb_alloc(unsigned long size, gfp_t gfp_mask)spin_unlock (&vbq->lock); } -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock);break ; }
@@ -2144,16 +2145,16 @@ static void vb_free(unsigned long addr, unsigned long size) order =get_order (size); offset = (addr & (VMAP_BLOCK_SIZE -1 )) >>PAGE_SHIFT ; vb =xa_load (&vmap_blocks,addr_to_vb_idx (addr)); -spin_lock (&vb->lock); +mutex_lock (&vb->lock);bitmap_clear (vb->used_map, offset, (1UL << order)); -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock);vunmap_range_noflush (addr, addr + size);if (debug_pagealloc_enabled_static ())flush_tlb_kernel_range (addr, addr + size); -spin_lock (&vb->lock); +mutex_lock (&vb->lock);/* Expand dirty range */ vb->dirty_min =min (vb->dirty_min, offset);
@@ -2162,10 +2163,10 @@ static void vb_free(unsigned long addr, unsigned long size) vb->dirty +=1UL << order;if (vb->dirty ==VMAP_BBMAP_BITS ) {BUG_ON (vb->free); -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock);free_vmap_block (vb); }else -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock); }static void _vm_unmap_aliases (unsigned long start,unsigned long end,int flush)
@@ -2183,7 +2184,7 @@ static void _vm_unmap_aliases(unsigned long start, unsigned long end, int flush)rcu_read_lock ();list_for_each_entry_rcu (vb, &vbq->free, free_list) { -spin_lock (&vb->lock); +mutex_lock (&vb->lock);if (vb->dirty && vb->dirty !=VMAP_BBMAP_BITS ) {unsigned long va_start = vb->va->va_start;unsigned long s, e;
@@ -2196,7 +2197,7 @@ static void _vm_unmap_aliases(unsigned long start, unsigned long end, int flush) flush =1 ; } -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock); }rcu_read_unlock (); }
@@ -2451,9 +2452,9 @@ static inline void setup_vmalloc_vm_locked(struct vm_struct *vm,static void setup_vmalloc_vm (struct vm_struct *vm,struct vmap_area *va,unsigned long flags,const void *caller) { -spin_lock (&vmap_area_lock); +down_write (&vmap_area_lock);setup_vmalloc_vm_locked (vm, va, flags, caller); -spin_unlock (&vmap_area_lock); +up_write (&vmap_area_lock); }static void clear_vm_uninitialized_flag (struct vm_struct *vm)
@@ -3507,9 +3508,9 @@ static void vmap_ram_vread(char *buf, char *addr, int count, unsigned long flagsif (!vb) goto finished; -spin_lock (&vb->lock); +mutex_lock (&vb->lock);if (bitmap_empty (vb->used_map,VMAP_BBMAP_BITS )) { -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock); goto finished; }for_each_set_bitrange (rs, re, vb->used_map,VMAP_BBMAP_BITS ) {
@@ -3536,7 +3537,7 @@ static void vmap_ram_vread(char *buf, char *addr, int count, unsigned long flags count -= n; } unlock: -spin_unlock (&vb->lock); +mutex_unlock (&vb->lock); finished:/* zero-fill the left dirty or free regions */
@@ -3576,13 +3577,15 @@ long vread(char *buf, char *addr, unsigned long count)unsigned long buflen = count;unsigned long n, size, flags; +might_sleep (); + addr =kasan_reset_tag (addr);/* Don't allow overflow */ if ((unsigned long ) addr + count < count) count = -(unsigned long ) addr; -spin_lock (&vmap_area_lock); +down_read (&vmap_area_lock); va =find_vmap_area_exceed_addr ((unsigned long )addr);if (!va) goto finished;
@@ -3639,7 +3642,7 @@ long vread(char *buf, char *addr, unsigned long count) count -= n; } finished: -spin_unlock (&vmap_area_lock); +up_read (&vmap_area_lock);if (buf == buf_start)return 0 ;
@@ -3980,14 +3983,14 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets, }/* insert all vm's */ -spin_lock (&vmap_area_lock); +down_write (&vmap_area_lock);for (area =0 ; area < nr_vms; area++) {insert_vmap_area (vas[area], &vmap_area_root, &vmap_area_list);setup_vmalloc_vm_locked (vms[area], vas[area],VM_ALLOC , pcpu_get_vm_areas); } -spin_unlock (&vmap_area_lock); +up_write (&vmap_area_lock);/* * Mark allocated areas as accessible. Do it now as a best-effort
@@ -4114,7 +4117,7 @@ static void *s_start(struct seq_file *m, loff_t *pos) __acquires(&vmap_area_lock) {mutex_lock (&vmap_purge_lock); -spin_lock (&vmap_area_lock); +down_read (&vmap_area_lock);return seq_list_start (&vmap_area_list, *pos); }
@@ -4128,7 +4131,7 @@ static void s_stop(struct seq_file *m, void *p) __releases(&vmap_area_lock) __releases(&vmap_purge_lock) { -spin_unlock (&vmap_area_lock); +up_read (&vmap_area_lock);mutex_unlock (&vmap_purge_lock); }
Signature · 1 line
2.39.2
kernel test robot
build bot[lkp] [+328 bytes kernel size regression] [i386-tinyconfig] [1b7c843021] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
FYI, we noticed a +328 bytes kernel size regression due to commit:
FYI, we noticed a +328 bytes kernel size regression due to commit:
commit: 1b7c843021466759acc93c26bf63bfdabf51398c (mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock)
url: https://github.com/intel-lab-lkp/linux/commits/Lorenzo-Stoakes/fs-proc-kcore-Avoid-bounce-buffer-for-ktext-data/20230319-151114
patch link: https://lore.kernel.org/all/6c7f1ac0aeb55faaa46a09108d3999e4595870d9.1679209395.git.lstoakes@gmail.com/
patch subject: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Details as below (size data is obtained by nm --size-sort vmlinux):
fcb76393: fs/proc/kcore: Avoid bounce buffer for ktext data 1b7c8430: mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
+----------------------------------+----------+----------+-------+ | symbol | fcb76393 | 1b7c8430 | delta | +----------------------------------+----------+----------+-------+ | bzImage | 501536 | 501760 | 224 | | nm.t.vb_free | 178 | 210 | 32 | | nm.T.find_vmap_area | 6 | 36 | 30 | | nm.t.purge_fragmented_blocks | 276 | 306 | 30 | | nm.T.vread | 345 | 373 | 28 | | nm.t.vb_alloc | 196 | 222 | 26 | | nm.t.find_unlink_vmap_area | 49 | 73 | 24 | | nm.t.alloc_vmap_area | 1264 | 1288 | 24 | | nm.t.free_vmap_block | 117 | 140 | 23 | | nm.t.__get_vm_area_node | 218 | 240 | 22 | | nm.t._vm_unmap_aliases | 148 | 168 | 20 | | nm.t.free_vmap_area | 676 | 696 | 20 | | nm.t.new_vmap_block | 339 | 357 | 18 | | nm.d.vmap_area_lock | 0 | 16 | 16 | | nm.t.vmap_ram_vread | 259 | 272 | 13 | | nm.T.remap_vmalloc_range_partial | 164 | 166 | 2 | +----------------------------------+----------+----------+-------+
Thanks
Andrew Morton
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
In reply to Lorenzo StoakesQuoted 22 lines · On Sun, 19 Mar 2023 07:09:31 +0000 Lorenzo Stoakes <[email protected]> wrote:
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%
Quoted 8 lines
This represents a tiny reduction in performance that sits barely above noise.
The reason for making this change is to build a basis for
vread()to be usable asynchronously, this eliminating the need for a bounce buffer when copying data to userland inread_kcore()and allowing that to be converted to an iterator form.
I'm not understanding the final paragraph. How and where is vread() used "asynchronously"?
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 27 lines · Andrew Morton
Quoted 25 lines · On Sun, 19 Mar 2023 07:09:31 +0000 Lorenzo Stoakes <[email protected]> wrote:
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%This represents a tiny reduction in performance that sits barely above noise.
Quoted 8 lines
Quoted 5 lines
The reason for making this change is to build a basis for
vread()to be usable asynchronously, this eliminating the need for a bounce buffer when copying data to userland inread_kcore()and allowing that to be converted to an iterator form.I'm not understanding the final paragraph. How and where is
vread()used "asynchronously"?
The basis for saying asynchronous was based on Documentation/filesystems/vfs.rst describing read_iter() as 'possibly asynchronous read with iov_iter as destination', and read_iter() is what is (now) invoked when accessing /proc/kcore.
However I agree this is vague and it is clearer to refer to the fact that we are now directly writing to user memory and thus wish to avoid spinlocks as we may need to fault in user memory in doing so.
Would it be ok for you to go ahead and replace that final paragraph with the below?:-
The reason for making this change is to build a basis for vread() to write to user memory directly via an iterator; as a result we may cause page faults during which we must not hold a spinlock. Doing this eliminates the need for a bounce buffer in read_kcore() and thus permits that to be converted to also use an iterator, as a read_iter() handler.
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 9 lines · Lorenzo Stoakes
The basis for saying asynchronous was based on
Documentation/filesystems/vfs.rstdescribingread_iter()as 'possibly asynchronous read withiov_iteras destination', andread_iter()is what is (now) invoked when accessing/proc/kcore.However I agree this is vague and it is clearer to refer to the fact that we are now directly writing to user memory and thus wish to avoid spinlocks as we may need to fault in user memory in doing so.
Quoted 8 lines
Would it be ok for you to go ahead and replace that final paragraph with the below?:-
The reason for making this change is to build a basis for
vread()to write to user memory directly via an iterator; as a result we may cause page faults during which we must not hold a spinlock. Doing this eliminates the need for a bounce buffer inread_kcore()and thus permits that to be converted to also use an iterator, as aread_iter()handler.
I'd say the purpose of the iterator is to abstract whether we're accessing user memory, kernel memory or a pipe, so I'd suggest:
The reason for making this change is to build a basis for vread() to write to memory via an iterator; as a result we may cause page faults during which we must not hold a spinlock. Doing this eliminates the need for a bounce buffer in read_kcore() and thus permits that to be converted to also use an iterator, as a read_iter() handler.
I'm still undecided whether this change is really a good thing. I think we have line-of-sight to making vmalloc (and thus kvmalloc) usable from interrupt context, and this destroys that possibility.
I wonder if we can't do something like prefaulting the page before taking the spinlock, then use copy_page_to_iter_atomic()
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 19 lines · Matthew Wilcox
Quoted 9 lines · Lorenzo Stoakes
The basis for saying asynchronous was based on
Documentation/filesystems/vfs.rstdescribingread_iter()as 'possibly asynchronous read withiov_iteras destination', andread_iter()is what is (now) invoked when accessing/proc/kcore.However I agree this is vague and it is clearer to refer to the fact that we are now directly writing to user memory and thus wish to avoid spinlocks as we may need to fault in user memory in doing so.
Quoted 8 lines
Would it be ok for you to go ahead and replace that final paragraph with the below?:-
The reason for making this change is to build a basis for
vread()to write to user memory directly via an iterator; as a result we may cause page faults during which we must not hold a spinlock. Doing this eliminates the need for a bounce buffer inread_kcore()and thus permits that to be converted to also use an iterator, as aread_iter()handler.
Quoted 9 lines
I'd say the purpose of the iterator is to abstract whether we're accessing user memory, kernel memory or a pipe, so I'd suggest:
The reason for making this change is to build a basis for
vread()to write to memory via an iterator; as a result we may cause page faults during which we must not hold a spinlock. Doing this eliminates the need for a bounce buffer inread_kcore()and thus permits that to be converted to also use an iterator, as aread_iter()handler.
Thanks, sorry I missed the detail about iterators abstacting the three different targets there, that is definitely better!
Quoted 6 lines
I'm still undecided whether this change is really a good thing. I think we have line-of-sight to making vmalloc (and thus kvmalloc) usable from interrupt context, and this destroys that possibility.
I wonder if we can't do something like prefaulting the page before taking the spinlock, then use
copy_page_to_iter_atomic()
There are a number of aspects of vmalloc that are not atomic-safe, e.g. alloc_vmap_area() and vmap_range_noflush() are designated might_sleep(), equally vfree().
So I feel that making it safe for atomic context requires a bit more of a general rework. Given we would be able to revisit lock types at the point we do that (something that would fit very solidly into the context of any such change), and given that this patch series establishes that we use an iterator, I think it is useful to keep this as-is as defer that change until later.
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 4 lines · Lorenzo Stoakes
Quoted 2 lines · Matthew Wilcox
I wonder if we can't do something like prefaulting the page before taking the spinlock, then use
copy_page_to_iter_atomic()
<snip>
On second thoughts, and discussing with Uladzislau, this seems like a more sensible approach.
I'm going to respin with prefaulting and revert to the previous locking.
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
In reply to Lorenzo StoakesQuoted 22 lines
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%
Quoted 3 lines
This represents a tiny reduction in performance that sits barely above noise.
How important to have many simultaneous users of vread()? I do not see a big reason to switch into mutexes due to performance impact and making it less atomic.
So, how important for you to have this change?
--
Uladzislau Rezki
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 22 lines · Uladzislau Rezki
Quoted 22 lines
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%
Quoted 6 lines
Quoted 3 lines
This represents a tiny reduction in performance that sits barely above noise.
How important to have many simultaneous users of
vread()? I do not see a big reason to switch into mutexes due to performance impact and making it less atomic.
It's less about simultaneous users of vread() and more about being able to write direct to user memory rather than via a bounce buffer and not hold a spinlock over possible page faults.
The performance impact is barely above noise (I got fairly widely varying results), so I don't think it's really much of a cost at all. I can't imagine there are many users critically dependent on a sub-single digit % reduction in speed in vmalloc() allocation.
As I was saying to Willy, the code is already not atomic, or rather needs rework to become atomic-safe (there are a smattering of might_sleep()'s throughout)
However, given your objection alongside Willy's, let me examine Willy's suggestion that we instead of doing this, prefault the user memory in advance of the vread call.
Quoted 3 lines
So, how important for you to have this change?
Personally, always very important :)
Quoted 2 lines
--
Uladzislau Rezki
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 39 lines · Lorenzo Stoakes
Quoted 22 lines · Uladzislau Rezki
Quoted 22 lines
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%Quoted 6 lines
Quoted 3 lines
This represents a tiny reduction in performance that sits barely above noise.
How important to have many simultaneous users of
vread()? I do not see a big reason to switch into mutexes due to performance impact and making it less atomic.It's less about simultaneous users of
vread()and more about being able to write direct to user memory rather than via a bounce buffer and not hold a spinlock over possible page faults.The performance impact is barely above noise (I got fairly widely varying results), so I don't think it's really much of a cost at all. I can't imagine there are many users critically dependent on a sub-single digit % reduction in speed in
vmalloc()allocation.
Quoted 7 lines
As I was saying to Willy, the code is already not atomic, or rather needs rework to become atomic-safe (there are a smattering of might_sleep()'s throughout)
However, given your objection alongside Willy's, let me examine Willy's suggestion that we instead of doing this, prefault the user memory in advance of the vread call.
Just a quick perf tests shows regression around 6%. 10 workers test_mask is 31:
# default [ 140.349731] All test took worker0=485061693537 cycles [ 140.386065] All test took worker1=486504572954 cycles [ 140.418452] All test took worker2=467204082542 cycles [ 140.435895] All test took worker3=512591010219 cycles [ 140.458316] All test took worker4=448583324125 cycles [ 140.494244] All test took worker5=501018129647 cycles [ 140.518144] All test took worker6=516224787767 cycles [ 140.535472] All test took worker7=442025617137 cycles [ 140.558249] All test took worker8=503337286539 cycles [ 140.590571] All test took worker9=494369561574 cycles
# patch [ 144.464916] All test took worker0=530373399067 cycles [ 144.492904] All test took worker1=522641540924 cycles [ 144.528999] All test took worker2=529711158267 cycles [ 144.552963] All test took worker3=527389011775 cycles [ 144.592951] All test took worker4=529583252449 cycles [ 144.610286] All test took worker5=523605706016 cycles [ 144.627690] All test took worker6=531494777011 cycles [ 144.653046] All test took worker7=527150114726 cycles [ 144.669818] All test took worker8=526599712235 cycles [ 144.693428] All test took worker9=526057490851 cycles
Quoted 6 lines
Quoted 3 lines
So, how important for you to have this change?
Personally, always very important :)
This is good. Personal opinion always wins :)
--
Uladzislau Rezki
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 61 lines · Uladzislau Rezki
Quoted 39 lines · Lorenzo Stoakes
Quoted 22 lines · Uladzislau Rezki
Quoted 22 lines
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%Quoted 6 lines
Quoted 3 lines
This represents a tiny reduction in performance that sits barely above noise.
How important to have many simultaneous users of
vread()? I do not see a big reason to switch into mutexes due to performance impact and making it less atomic.It's less about simultaneous users of
vread()and more about being able to write direct to user memory rather than via a bounce buffer and not hold a spinlock over possible page faults.The performance impact is barely above noise (I got fairly widely varying results), so I don't think it's really much of a cost at all. I can't imagine there are many users critically dependent on a sub-single digit % reduction in speed in
vmalloc()allocation.Quoted 7 lines
As I was saying to Willy, the code is already not atomic, or rather needs rework to become atomic-safe (there are a smattering of might_sleep()'s throughout)
However, given your objection alongside Willy's, let me examine Willy's suggestion that we instead of doing this, prefault the user memory in advance of the vread call.
Just a quick perf tests shows regression around 6%. 10 workers
test_maskis 31:# default [ 140.349731] All test took worker0=485061693537 cycles [ 140.386065] All test took worker1=486504572954 cycles [ 140.418452] All test took worker2=467204082542 cycles [ 140.435895] All test took worker3=512591010219 cycles [ 140.458316] All test took worker4=448583324125 cycles [ 140.494244] All test took worker5=501018129647 cycles [ 140.518144] All test took worker6=516224787767 cycles [ 140.535472] All test took worker7=442025617137 cycles [ 140.558249] All test took worker8=503337286539 cycles [ 140.590571] All test took worker9=494369561574 cycles
Quoted 12 lines
# patch [ 144.464916] All test took worker0=530373399067 cycles [ 144.492904] All test took worker1=522641540924 cycles [ 144.528999] All test took worker2=529711158267 cycles [ 144.552963] All test took worker3=527389011775 cycles [ 144.592951] All test took worker4=529583252449 cycles [ 144.610286] All test took worker5=523605706016 cycles [ 144.627690] All test took worker6=531494777011 cycles [ 144.653046] All test took worker7=527150114726 cycles [ 144.669818] All test took worker8=526599712235 cycles [ 144.693428] All test took worker9=526057490851 cycles
OK ouch, that's worse than I observed! Let me try this prefault approach and then we can revert back to spinlocks.
Quoted 8 lines
Quoted 6 lines
Quoted 3 lines
So, how important for you to have this change?
Personally, always very important :)
This is good. Personal opinion always wins :)
The heart always wins ;) well, an adaption here can make everybody's hearts happy I think.
Quoted 2 lines
--
Uladzislau Rezki
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 82 lines · Lorenzo Stoakes
Quoted 61 lines · Uladzislau Rezki
Quoted 39 lines · Lorenzo Stoakes
Quoted 22 lines · Uladzislau Rezki
Quoted 22 lines
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%Quoted 6 lines
Quoted 3 lines
This represents a tiny reduction in performance that sits barely above noise.
How important to have many simultaneous users of
vread()? I do not see a big reason to switch into mutexes due to performance impact and making it less atomic.It's less about simultaneous users of
vread()and more about being able to write direct to user memory rather than via a bounce buffer and not hold a spinlock over possible page faults.The performance impact is barely above noise (I got fairly widely varying results), so I don't think it's really much of a cost at all. I can't imagine there are many users critically dependent on a sub-single digit % reduction in speed in
vmalloc()allocation.Quoted 7 lines
As I was saying to Willy, the code is already not atomic, or rather needs rework to become atomic-safe (there are a smattering of might_sleep()'s throughout)
However, given your objection alongside Willy's, let me examine Willy's suggestion that we instead of doing this, prefault the user memory in advance of the vread call.
Just a quick perf tests shows regression around 6%. 10 workers
test_maskis 31:# default [ 140.349731] All test took worker0=485061693537 cycles [ 140.386065] All test took worker1=486504572954 cycles [ 140.418452] All test took worker2=467204082542 cycles [ 140.435895] All test took worker3=512591010219 cycles [ 140.458316] All test took worker4=448583324125 cycles [ 140.494244] All test took worker5=501018129647 cycles [ 140.518144] All test took worker6=516224787767 cycles [ 140.535472] All test took worker7=442025617137 cycles [ 140.558249] All test took worker8=503337286539 cycles [ 140.590571] All test took worker9=494369561574 cyclesQuoted 12 lines
# patch [ 144.464916] All test took worker0=530373399067 cycles [ 144.492904] All test took worker1=522641540924 cycles [ 144.528999] All test took worker2=529711158267 cycles [ 144.552963] All test took worker3=527389011775 cycles [ 144.592951] All test took worker4=529583252449 cycles [ 144.610286] All test took worker5=523605706016 cycles [ 144.627690] All test took worker6=531494777011 cycles [ 144.653046] All test took worker7=527150114726 cycles [ 144.669818] All test took worker8=526599712235 cycles [ 144.693428] All test took worker9=526057490851 cyclesOK ouch, that's worse than I observed! Let me try this prefault approach and then we can revert back to spinlocks.
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
So, how important for you to have this change?
Quoted 8 lines
Quoted 4 lines
Quoted 2 lines
Personally, always very important :)
This is good. Personal opinion always wins :)
The heart always wins ;) well, an adaption here can make everybody's hearts happy I think.
Totally agree :)
--
Uladzislau Rezki
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
In reply to Lorenzo StoakesQuoted 22 lines · Lorenzo Stoakes
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%
Quoted 2 lines
This represents a tiny reduction in performance that sits barely above noise.
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use vm_map_ram and vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
-Dave.
Signature · 2 lines
Dave Chinner [email protected]
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 36 lines · Dave Chinner
Quoted 22 lines · Lorenzo Stoakes
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%Quoted 2 lines
This represents a tiny reduction in performance that sits barely above noise.
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.
Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
I am working on it. I submitted a proposal how to eliminate it:
<snip>
Hello, LSF.
Title: Introduce a per-cpu-vmap-cache to eliminate a vmap lock contention
Description:
Currently the vmap code is not scaled to number of CPU cores in a system because a global vmap space is protected by a single spinlock. Such approach has a clear bottleneck if many CPUs simultaneously access to one resource.
In this talk i would like to describe a drawback, show some data related to contentions and places where those occur in a code. Apart of that i would like to share ideas how to eliminate it providing a few approaches and compare them.
Requirements:
* It should be a per-cpu approach;
* Search of freed ptrs should not interfere with other freeing(as much as we can);
* - offload allocated areas(buzy ones) per-cpu;
* Cache ready sized objects or merge them into one big per-cpu-space(split on demand);
* Lazily-freed areas either drained per-cpu individually or by one CPU for all;
* Prefetch a fixed size in front and allocate per-cpu
Goals:
* Implement a per-cpu way of allocation to eliminate a contention.
Thanks!
<snip>
--
Uladzislau Rezki
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 37 lines · Uladzislau Rezki
Quoted 36 lines · Dave Chinner
Quoted 22 lines · Lorenzo Stoakes
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%Quoted 2 lines
This represents a tiny reduction in performance that sits barely above noise.
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.
Quoted 3 lines
Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Hugely appreciated Dave, however I must disappoint on the rwsem as I have now reworked my patch set to use the original locks in order to satisfy Willy's desire to make vmalloc atomic in future, and Uladzislau's desire to not have a ~6% performance hit -
https://lore.kernel.org/all/[email protected]/
Quoted 26 lines
I am working on it. I submitted a proposal how to eliminate it:
<snip>
Hello, LSF.Title: Introduce a per-cpu-vmap-cache to eliminate a vmap lock contention
Description:
Currently the vmap code is not scaled to number of CPU cores in a system because a global vmap space is protected by a single spinlock. Such approach has a clear bottleneck if many CPUs simultaneously access to one resource.In this talk i would like to describe a drawback, show some data related to contentions and places where those occur in a code. Apart of that i would like to share ideas how to eliminate it providing a few approaches and compare them.
Requirements:
* It should be a per-cpu approach;
* Search of freed ptrs should not interfere with other freeing(as much as we can);
* - offload allocated areas(buzy ones) per-cpu;
* Cache ready sized objects or merge them into one big per-cpu-space(split on demand);
* Lazily-freed areas either drained per-cpu individually or by one CPU for all;
* Prefetch a fixed size in front and allocate per-cpu
Quoted 9 lines
Goals:
* Implement a per-cpu way of allocation to eliminate a contention.Thanks!
<snip>--
Uladzislau Rezki
That's really awesome! I will come to that talk at LSF/MM :) being able to sustain the lock in atomic context seems to be an aspect that is important going forward also.
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 80 lines · Lorenzo Stoakes
Quoted 37 lines · Uladzislau Rezki
Quoted 36 lines · Dave Chinner
Quoted 22 lines · Lorenzo Stoakes
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%Quoted 2 lines
This represents a tiny reduction in performance that sits barely above noise.
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.Quoted 3 lines
Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Hugely appreciated Dave, however I must disappoint on the rwsem as I have now reworked my patch set to use the original locks in order to satisfy Willy's desire to make vmalloc atomic in future, and Uladzislau's desire to not have a ~6% performance hit -
https://lore.kernel.org/all/[email protected]/Quoted 32 lines
I am working on it. I submitted a proposal how to eliminate it:
<snip>
Hello, LSF.Title: Introduce a per-cpu-vmap-cache to eliminate a vmap lock contention
Description:
Currently the vmap code is not scaled to number of CPU cores in a system because a global vmap space is protected by a single spinlock. Such approach has a clear bottleneck if many CPUs simultaneously access to one resource.In this talk i would like to describe a drawback, show some data related to contentions and places where those occur in a code. Apart of that i would like to share ideas how to eliminate it providing a few approaches and compare them.
Requirements:
* It should be a per-cpu approach;
* Search of freed ptrs should not interfere with other freeing(as much as we can);
* - offload allocated areas(buzy ones) per-cpu;
* Cache ready sized objects or merge them into one big per-cpu-space(split on demand);
* Lazily-freed areas either drained per-cpu individually or by one CPU for all;
* Prefetch a fixed size in front and allocate per-cpuGoals:
* Implement a per-cpu way of allocation to eliminate a contention.Thanks!
<snip>
Quoted 8 lines
Quoted 3 lines
--
Uladzislau RezkiThat's really awesome! I will come to that talk at LSF/MM :) being able to sustain the lock in atomic context seems to be an aspect that is important going forward also.
Uhh... So i need to prepare then :)))
--
Uladzislau Rezki
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
In reply to Lorenzo StoakesQuoted 42 lines · Lorenzo Stoakes
Quoted 37 lines · Uladzislau Rezki
Quoted 36 lines · Dave Chinner
Quoted 22 lines · Lorenzo Stoakes
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%Quoted 2 lines
This represents a tiny reduction in performance that sits barely above noise.
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.Quoted 3 lines
Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Quoted 5 lines
Hugely appreciated Dave, however I must disappoint on the rwsem as I have now reworked my patch set to use the original locks in order to satisfy Willy's desire to make vmalloc atomic in future, and Uladzislau's desire to not have a ~6% performance hit -
https://lore.kernel.org/all/[email protected]/
Yeah, I'd already read that.
What I want to do, though, is to determine whether the problem shared access contention or exclusive access contention. If it's exclusive access contention, then an rwsem will do nothing to alleviate the problem, and that's kinda critical to know before any fix for the contention problems are worked out...
Quoted 13 lines
Quoted 13 lines
I am working on it. I submitted a proposal how to eliminate it:
<snip>
Hello, LSF.Title: Introduce a per-cpu-vmap-cache to eliminate a vmap lock contention
Description:
Currently the vmap code is not scaled to number of CPU cores in a system because a global vmap space is protected by a single spinlock. Such approach has a clear bottleneck if many CPUs simultaneously access to one resource.
Quoted 4 lines
Quoted 4 lines
In this talk i would like to describe a drawback, show some data related to contentions and places where those occur in a code. Apart of that i would like to share ideas how to eliminate it providing a few approaches and compare them.
If you want data about contention problems with vmalloc
Quoted 2 lines
Quoted 2 lines
Requirements:
* It should be a per-cpu approach;
Hmmmm. My 2c worth on this: That is not a requirement.
That's a -solution-.
The requirement is that independent concurrent vmalloc/vfree operations do not severely contend with each other.
Yes, the solution will probably involve sharding the resource space across mulitple independent structures (as we do in filesystems with block groups, allocations groups, etc) but that does not necessarily need the structures to be per-cpu.
e.g per-node vmalloc arenas might be sufficient and allow more expensive but more efficient indexing structures to be used because we don't have to care about the explosion of memory that fine-grained per-cpu indexing generally entails. This may also fit in to the existing per-node structure of the memory reclaim infrastructure to manage things like compaction, balancing, etc of vmalloc space assigned to the given node.
Hence I think saying "per-cpu is a requirement" kinda prevents exploration of other novel solutions that may have advantages other than "just solves the concurrency problem"...
Quoted 5 lines
Quoted 5 lines
* Search of freed ptrs should not interfere with other freeing(as much as we can);
* - offload allocated areas(buzy ones) per-cpu;
* Cache ready sized objects or merge them into one big per-cpu-space(split on demand);
* Lazily-freed areas either drained per-cpu individually or by one CPU for all;
* Prefetch a fixed size in front and allocate per-cpu
I'd call these desired traits and/or potential optimisations, not hard requirements.
Quoted 2 lines
Quoted 2 lines
Goals:
* Implement a per-cpu way of allocation to eliminate a contention.
The goal should be to "allow contention-free vmalloc operations", not that we implement a specific solution.
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 110 lines · Dave Chinner
Quoted 42 lines · Lorenzo Stoakes
Quoted 37 lines · Uladzislau Rezki
Quoted 36 lines · Dave Chinner
Quoted 22 lines · Lorenzo Stoakes
vmalloc()is, by design, not permitted to be used in atomic context and already contains components which may sleep, so avoiding spin locks is not a problem from the perspective of atomic context.The global
vmap_area_lockis held when the red/black tree rooted invmap_are_rootis accessed and thus is rather long-held and under potentially high contention. It is likely to be under contention for reads rather than write, so replace it with a rwsem.Each individual
vmap_block->lockis likely to be held for less time but under low contention, so a mutex is not an outrageous choice here.A subset of
test_vmalloc.shperformance results:-
fix_size_alloc_test0.40%full_fit_alloc_test2.08%long_busy_list_alloc_test0.34%random_size_alloc_test-0.25%random_size_align_alloc_test0.06%
...
all tests cycles 0.2%Quoted 2 lines
This represents a tiny reduction in performance that sits barely above noise.
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.Quoted 3 lines
Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Quoted 5 lines
Hugely appreciated Dave, however I must disappoint on the rwsem as I have now reworked my patch set to use the original locks in order to satisfy Willy's desire to make vmalloc atomic in future, and Uladzislau's desire to not have a ~6% performance hit -
https://lore.kernel.org/all/[email protected]/Yeah, I'd already read that.
What I want to do, though, is to determine whether the problem shared access contention or exclusive access contention. If it's exclusive access contention, then an rwsem will do nothing to alleviate the problem, and that's kinda critical to know before any fix for the contention problems are worked out...
Quoted 13 lines
Quoted 13 lines
I am working on it. I submitted a proposal how to eliminate it:
<snip>
Hello, LSF.Title: Introduce a per-cpu-vmap-cache to eliminate a vmap lock contention
Description:
Currently the vmap code is not scaled to number of CPU cores in a system because a global vmap space is protected by a single spinlock. Such approach has a clear bottleneck if many CPUs simultaneously access to one resource.Quoted 4 lines
Quoted 4 lines
In this talk i would like to describe a drawback, show some data related to contentions and places where those occur in a code. Apart of that i would like to share ideas how to eliminate it providing a few approaches and compare them.
If you want data about contention problems with vmalloc
Quoted 2 lines
Quoted 2 lines
Requirements:
* It should be a per-cpu approach;Hmmmm. My 2c worth on this: That is not a requirement.
That's a -solution-.
The requirement is that independent concurrent vmalloc/vfree operations do not severely contend with each other.
Yes, the solution will probably involve sharding the resource space across mulitple independent structures (as we do in filesystems with block groups, allocations groups, etc) but that does not necessarily need the structures to be per-cpu.
e.g per-node vmalloc arenas might be sufficient and allow more expensive but more efficient indexing structures to be used because we don't have to care about the explosion of memory that fine-grained per-cpu indexing generally entails. This may also fit in to the existing per-node structure of the memory reclaim infrastructure to manage things like compaction, balancing, etc of vmalloc space assigned to the given node.
Hence I think saying "per-cpu is a requirement" kinda prevents exploration of other novel solutions that may have advantages other than "just solves the concurrency problem"...
Quoted 5 lines
Quoted 5 lines
* Search of freed ptrs should not interfere with other freeing(as much as we can);
* - offload allocated areas(buzy ones) per-cpu;
* Cache ready sized objects or merge them into one big per-cpu-space(split on demand);
* Lazily-freed areas either drained per-cpu individually or by one CPU for all;
* Prefetch a fixed size in front and allocate per-cpu
Quoted 9 lines
I'd call these desired traits and/or potential optimisations, not hard requirements.
Quoted 2 lines
Quoted 2 lines
Goals:
* Implement a per-cpu way of allocation to eliminate a contention.The goal should be to "allow contention-free vmalloc operations", not that we implement a specific solution.
I think we are on the same page. I do not see that we go apart in anything. Probably i was a bit more specific in requirements but this is how i see personally on it based on different kind of experiments with it.
Thank you for your 2c!
--
Uladzislau Rezki
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
In reply to Dave ChinnerHello, Dave.
Quoted 11 lines
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.
Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Could you please share some steps how to run your workloads in order to touch vmalloc() code. I would like to have a look at it in more detail just for understanding the workloads.
Meanwhile my grep agains xfs shows:
<snip>
urezki@pc638:~/data/raid0/coding/linux-rcu.git/fs/xfs$ grep -rn vmalloc ./ ./xfs_log_priv.h:675: * Log vector and shadow buffers can be large, so we need to use kvmalloc() here ./xfs_log_priv.h:676: * to ensure success. Unfortunately, kvmalloc() only allows GFP_KERNEL contexts ./xfs_log_priv.h:677: * to fall back to vmalloc, so we can't actually do anything useful with gfp ./xfs_log_priv.h:678: * flags to control the kmalloc() behaviour within kvmalloc(). Hence kmalloc() ./xfs_log_priv.h:681: * vmalloc if it can't get somethign straight away from the free lists or ./xfs_log_priv.h:682: * buddy allocator. Hence we have to open code kvmalloc outselves here. ./xfs_log_priv.h:686: * allocations. This is actually the only way to make vmalloc() do GFP_NOFS ./xfs_log_priv.h:691:xlog_kvmalloc( ./xfs_log_priv.h:702: p = vmalloc(buf_size); ./xfs_bio_io.c:21: unsigned int is_vmalloc = is_vmalloc_addr(data); ./xfs_bio_io.c:26: if (is_vmalloc && op == REQ_OP_WRITE) ./xfs_bio_io.c:56: if (is_vmalloc && op == REQ_OP_READ) ./xfs_log.c:1976: if (is_vmalloc_addr(iclog->ic_data)) ./xfs_log_cil.c:338: lv = xlog_kvmalloc(buf_size); ./libxfs/xfs_attr_leaf.c:522: args->value = kvmalloc(valuelen, GFP_KERNEL | __GFP_NOLOCKDEP); ./kmem.h:12:#include <linux/vmalloc.h> ./kmem.h:78: if (is_vmalloc_addr(addr)) ./kmem.h:79: return vmalloc_to_page(addr); ./xfs_attr_item.c:84: * This could be over 64kB in length, so we have to use kvmalloc() for ./xfs_attr_item.c:85: * this. But kvmalloc() utterly sucks, so we use our own version. ./xfs_attr_item.c:87: nv = xlog_kvmalloc(sizeof(struct xfs_attri_log_nameval) + ./scrub/attr.c:60: ab = kvmalloc(sizeof(*ab) + sz, flags); urezki@pc638:~/data/raid0/coding/linux-rcu.git/fs/xfs$ <snip>
Thanks!
--
Uladzislau Rezki
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 16 lines · Uladzislau Rezki
Hello, Dave.
Quoted 11 lines
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Quoted 8 lines
Could you please share some steps how to run your workloads in order to touch
vmalloc()code. I would like to have a look at it in more detail just for understanding the workloads.Meanwhile my grep agains xfs shows:
<snip>
urezki@pc638:~/data/raid0/coding/linux-rcu.git/fs/xfs$ grep -rn vmalloc ./
You're missing:
fs/xfs/xfs_buf.c: bp->b_addr = vm_map_ram(bp->b_pages, bp->b_page_count,
which i suspect is the majority of Dave's workload. That will almost certainly take the vb_alloc() path.
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 26 lines · Matthew Wilcox
Quoted 16 lines · Uladzislau Rezki
Hello, Dave.
Quoted 11 lines
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Quoted 8 lines
Could you please share some steps how to run your workloads in order to touch
vmalloc()code. I would like to have a look at it in more detail just for understanding the workloads.Meanwhile my grep agains xfs shows:
<snip>
urezki@pc638:~/data/raid0/coding/linux-rcu.git/fs/xfs$ grep -rn vmalloc ./
Quoted 7 lines
You're missing:
fs/xfs/xfs_buf.c:bp->b_addr= vm_map_ram(bp->b_pages,bp->b_page_count,which i suspect is the majority of Dave's workload. That will almost certainly take the
vb_alloc()path.
Then it has nothing to do with vmalloc contention(i mean global KVA allocator), IMHO. Unless:
<snip>
void *vm_map_ram(struct page **pages, unsigned int count, int node)
{
unsigned long size = (unsigned long)count << PAGE_SHIFT;
unsigned long addr;
void *mem;
if (likely(count <= VMAP_MAX_ALLOC)) {
mem = vb_alloc(size, GFP_KERNEL);
if (IS_ERR(mem))
return NULL;
addr = (unsigned long)mem;
} else {
struct vmap_area *va;
va = alloc_vmap_area(size, PAGE_SIZE,
VMALLOC_START, VMALLOC_END, node, GFP_KERNEL);
if (IS_ERR(va))
return NULL;<snip>
number of pages > VMAP_MAX_ALLOC.
That is why i have asked about workloads because i would like to understand where a "problem" is. A vm_map_ram() access the global vmap space but it happens when a new vmap block is required and i also think it is not a problem.
But who knows, therefore it makes sense to have a lock at workload.
--
Uladzislau Rezki
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 57 lines · Uladzislau Rezki
Quoted 26 lines · Matthew Wilcox
Quoted 16 lines · Uladzislau Rezki
Hello, Dave.
Quoted 11 lines
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Quoted 8 lines
Could you please share some steps how to run your workloads in order to touch
vmalloc()code. I would like to have a look at it in more detail just for understanding the workloads.Meanwhile my grep agains xfs shows:
<snip>
urezki@pc638:~/data/raid0/coding/linux-rcu.git/fs/xfs$ grep -rn vmalloc ./Quoted 7 lines
You're missing:
fs/xfs/xfs_buf.c:bp->b_addr= vm_map_ram(bp->b_pages,bp->b_page_count,which i suspect is the majority of Dave's workload. That will almost certainly take the
vb_alloc()path.Then it has nothing to do with vmalloc contention(i mean global KVA allocator), IMHO. Unless:
<snip>
void *vm_map_ram(struct page **pages, unsigned int count, int node){ unsigned long size = (unsigned long)count << PAGE_SHIFT; unsigned long addr; void *mem; if (likely(count <= VMAP_MAX_ALLOC)) { mem = vb_alloc(size, GFP_KERNEL); if (IS_ERR(mem)) return NULL; addr = (unsigned long)mem; } else { struct vmap_area *va; va = alloc_vmap_area(size, PAGE_SIZE, VMALLOC_START, VMALLOC_END, node, GFP_KERNEL); if (IS_ERR(va)) return NULL;<snip>
Quoted 8 lines
number of pages >
VMAP_MAX_ALLOC.That is why i have asked about workloads because i would like to understand where a "problem" is. A
vm_map_ram()access the global vmap space but it happens when a new vmap block is required and i also think it is not a problem.But who knows, therefore it makes sense to have a lock at workload.
There is a lock-stat statistics for vm_map_ram()/vm_unmap_ram() test. I did it on 64 CPUs system with running 64 threads doing mapping/unmapping of 1 page. Each thread does 10 000 000 mapping + unmapping in a loop:
<snip>
root@pc638:/home/urezki# cat /proc/lock_stat lock_stat version 0.4 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
vmap_area_lock: 2554079 2554276 0.06 213.61 11719647.67 4.59 2986513 3005712 0.05 67.02 3573323.37 1.19
--------------
vmap_area_lock 1297948 [<00000000dd41cbaa>] alloc_vmap_area+0x1c7/0x910 vmap_area_lock 1256330 [<000000009d927bf3>] free_vmap_block+0x4a/0xe0 vmap_area_lock 1 [<00000000c95c05a7>] find_vm_area+0x16/0x70 -------------- vmap_area_lock 1738590 [<00000000dd41cbaa>] alloc_vmap_area+0x1c7/0x910 vmap_area_lock 815688 [<000000009d927bf3>] free_vmap_block+0x4a/0xe0 vmap_area_lock 1 [<00000000c1d619d7>] __get_vm_area_node+0xd2/0x170
.....................................................................................................................................................................................................
vmap_blocks.xa_lock: 862689 862698 0.05 77.74 849325.39 0.98 3005156 3005709 0.12 31.11 1920242.82 0.64
-------------------
vmap_blocks.xa_lock 378418 [<00000000625a5626>] vm_map_ram+0x359/0x4a0 vmap_blocks.xa_lock 484280 [<00000000caa2ef03>] xa_erase+0xe/0x30 ------------------- vmap_blocks.xa_lock 576226 [<00000000caa2ef03>] xa_erase+0xe/0x30 vmap_blocks.xa_lock 286472 [<00000000625a5626>] vm_map_ram+0x359/0x4a0
....................................................................................................................................................................................................
free_vmap_area_lock: 394960 394961 0.05 124.78 448241.23 1.13 1514508 1515077 0.12 30.48 1179167.01 0.78
-------------------
free_vmap_area_lock 385970 [<00000000955bd641>] alloc_vmap_area+0xe5/0x910 free_vmap_area_lock 4692 [<00000000230abf7e>] __purge_vmap_area_lazy+0x10a/0x7d0 free_vmap_area_lock 4299 [<00000000eed9ff9e>] alloc_vmap_area+0x497/0x910 ------------------- free_vmap_area_lock 371734 [<00000000955bd641>] alloc_vmap_area+0xe5/0x910 free_vmap_area_lock 17007 [<00000000230abf7e>] __purge_vmap_area_lazy+0x10a/0x7d0 free_vmap_area_lock 6220 [<00000000eed9ff9e>] alloc_vmap_area+0x497/0x910
.....................................................................................................................................................................................................
purge_vmap_area_lock: 169307 169312 0.05 31.08 81655.21 0.48 1514794 1515078 0.05 67.73 912391.12 0.60
--------------------
purge_vmap_area_lock 166409 [<0000000050938075>] free_vmap_area_noflush+0x65/0x370
purge_vmap_area_lock 2903 [<00000000fb8b57f7>] __purge_vmap_area_lazy+0x47/0x7d0
--------------------
purge_vmap_area_lock 167511 [<0000000050938075>] free_vmap_area_noflush+0x65/0x370
purge_vmap_area_lock 1801 [<00000000fb8b57f7>] __purge_vmap_area_lazy+0x47/0x7d0
<snip>
alloc_vmap_area is a top and second one is xa_lock. But the test i have done is pretty high concurrent scenario.
--
Uladzislau Rezki
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 120 lines · Uladzislau Rezki
Quoted 57 lines · Uladzislau Rezki
Quoted 26 lines · Matthew Wilcox
Quoted 16 lines · Uladzislau Rezki
Hello, Dave.
Quoted 11 lines
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Quoted 8 lines
Could you please share some steps how to run your workloads in order to touch
vmalloc()code. I would like to have a look at it in more detail just for understanding the workloads.Meanwhile my grep agains xfs shows:
<snip>
urezki@pc638:~/data/raid0/coding/linux-rcu.git/fs/xfs$ grep -rn vmalloc ./Quoted 7 lines
You're missing:
fs/xfs/xfs_buf.c:bp->b_addr= vm_map_ram(bp->b_pages,bp->b_page_count,which i suspect is the majority of Dave's workload. That will almost certainly take the
vb_alloc()path.Then it has nothing to do with vmalloc contention(i mean global KVA allocator), IMHO. Unless:
<snip>
void *vm_map_ram(struct page **pages, unsigned int count, int node){ unsigned long size = (unsigned long)count << PAGE_SHIFT; unsigned long addr; void *mem; if (likely(count <= VMAP_MAX_ALLOC)) { mem = vb_alloc(size, GFP_KERNEL); if (IS_ERR(mem)) return NULL; addr = (unsigned long)mem; } else { struct vmap_area *va; va = alloc_vmap_area(size, PAGE_SIZE, VMALLOC_START, VMALLOC_END, node, GFP_KERNEL); if (IS_ERR(va)) return NULL;<snip>
Quoted 8 lines
number of pages >
VMAP_MAX_ALLOC.That is why i have asked about workloads because i would like to understand where a "problem" is. A
vm_map_ram()access the global vmap space but it happens when a new vmap block is required and i also think it is not a problem.But who knows, therefore it makes sense to have a lock at workload.
There is a lock-stat statistics for vm_map_ram()/vm_unmap_ram() test. I did it on 64 CPUs system with running 64 threads doing mapping/unmapping of 1 page. Each thread does 10 000 000 mapping + unmapping in a loop:
<snip>
root@pc638:/home/urezki# cat /proc/lock_stat lock_stat version 0.4 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min holdtime-max holdtime-total holdtime-avg -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
vmap_area_lock: 2554079 2554276 0.06 213.61 11719647.67 4.59 2986513 3005712 0.05 67.02 3573323.37 1.19
--------------vmap_area_lock 1297948 [<00000000dd41cbaa>] alloc_vmap_area+0x1c7/0x910 vmap_area_lock 1256330 [<000000009d927bf3>] free_vmap_block+0x4a/0xe0 vmap_area_lock 1 [<00000000c95c05a7>] find_vm_area+0x16/0x70 -------------- vmap_area_lock 1738590 [<00000000dd41cbaa>] alloc_vmap_area+0x1c7/0x910 vmap_area_lock 815688 [<000000009d927bf3>] free_vmap_block+0x4a/0xe0 vmap_area_lock 1 [<00000000c1d619d7>] __get_vm_area_node+0xd2/0x170.....................................................................................................................................................................................................
vmap_blocks.xa_lock: 862689 862698 0.05 77.74 849325.39 0.98 3005156 3005709 0.12 31.11 1920242.82 0.64
-------------------vmap_blocks.xa_lock 378418 [<00000000625a5626>] vm_map_ram+0x359/0x4a0 vmap_blocks.xa_lock 484280 [<00000000caa2ef03>] xa_erase+0xe/0x30 ------------------- vmap_blocks.xa_lock 576226 [<00000000caa2ef03>] xa_erase+0xe/0x30 vmap_blocks.xa_lock 286472 [<00000000625a5626>] vm_map_ram+0x359/0x4a0....................................................................................................................................................................................................
free_vmap_area_lock: 394960 394961 0.05 124.78 448241.23 1.13 1514508 1515077 0.12 30.48 1179167.01 0.78
-------------------free_vmap_area_lock 385970 [<00000000955bd641>] alloc_vmap_area+0xe5/0x910 free_vmap_area_lock 4692 [<00000000230abf7e>] __purge_vmap_area_lazy+0x10a/0x7d0 free_vmap_area_lock 4299 [<00000000eed9ff9e>] alloc_vmap_area+0x497/0x910 ------------------- free_vmap_area_lock 371734 [<00000000955bd641>] alloc_vmap_area+0xe5/0x910 free_vmap_area_lock 17007 [<00000000230abf7e>] __purge_vmap_area_lazy+0x10a/0x7d0 free_vmap_area_lock 6220 [<00000000eed9ff9e>] alloc_vmap_area+0x497/0x910.....................................................................................................................................................................................................
purge_vmap_area_lock: 169307 169312 0.05 31.08 81655.21 0.48 1514794 1515078 0.05 67.73 912391.12 0.60
--------------------
purge_vmap_area_lock166409 [<0000000050938075>]free_vmap_area_noflush+0x65/0x370
purge_vmap_area_lock2903 [<00000000fb8b57f7>]__purge_vmap_area_lazy+0x47/0x7d0
--------------------
purge_vmap_area_lock167511 [<0000000050938075>]free_vmap_area_noflush+0x65/0x370
purge_vmap_area_lock1801 [<00000000fb8b57f7>]__purge_vmap_area_lazy+0x47/0x7d0
<snip>
Quoted 3 lines
alloc_vmap_areais a top and second one isxa_lock. But the test i have done is pretty high concurrent scenario.
<snip>
From 32c38d239c6de3f1d3accf97d9d4944ecaa4bccd Mon Sep 17 00:00:00 2001
From: "Uladzislau Rezki (Sony)" <[email protected]>
Date: Thu, 23 Mar 2023 13:07:27 +0100
Subject: [PATCH] mm: vmalloc: Remove global vmap_blocks xarray
A global vmap_blocks-xarray array can be contented under heavy usage of the vm_map_ram()/vm_unmap_ram() APIs. Under stress test the lock-stat shows that a "vmap_blocks.xa_lock" lock is a second in a list when it comes to contentions:
<snip>
----------------------------------------
class name con-bounces contentions ...
----------------------------------------
...
vmap_blocks.xa_lock: 862689 862698 ...
-------------------
vmap_blocks.xa_lock 378418 [<00000000625a5626>] vm_map_ram+0x359/0x4a0 vmap_blocks.xa_lock 484280 [<00000000caa2ef03>] xa_erase+0xe/0x30 ------------------- vmap_blocks.xa_lock 576226 [<00000000caa2ef03>] xa_erase+0xe/0x30 vmap_blocks.xa_lock 286472 [<00000000625a5626>] vm_map_ram+0x359/0x4a0
...
<snip>
that is a result of running vm_map_ram()/vm_unmap_ram() in a loop. The test creates 64(on 64 CPUs system) threads and each one maps/unmaps 1 page.
After this change the xa_lock is considered as noise in the same test condition:
<snip>
...
&xa->xa_lock#1: 10333 10394 ...
--------------
&xa->xa_lock#1 5349 [<00000000bbbc9751>] xa_erase+0xe/0x30 &xa->xa_lock#1 5045 [<0000000018def45d>] vm_map_ram+0x3a4/0x4f0 -------------- &xa->xa_lock#1 7326 [<0000000018def45d>] vm_map_ram+0x3a4/0x4f0 &xa->xa_lock#1 3068 [<00000000bbbc9751>] xa_erase+0xe/0x30
...
<snip>
--- mm/vmalloc.c | 54 +++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 24 deletions(-)
mm/vmalloc.c +30 -24
@@ -1911,6 +1911,7 @@ static struct vmap_area *find_unlink_vmap_area(unsigned long addr)struct vmap_block_queue {spinlock_t lock;struct list_head free; +struct xarray vmap_blocks; };struct vmap_block {
@@ -1927,25 +1928,22 @@ struct vmap_block {/* Queue of free and dirty vmap blocks, for allocation and flushing purposes */ static DEFINE_PER_CPU(struct vmap_block_queue, vmap_block_queue); -/* -* XArray of vmap blocks, indexed by address, to quickly find a vmap block -* in the free path. Could get rid of this if we change the API to return a -* "cookie" from alloc, to be passed to free. But no big deal yet. -*/ -static DEFINE_XARRAY (vmap_blocks); - -/* -* We should probably have a fallback mechanism to allocate virtual memory -* out of partially filled vmap blocks. However vmap block sizing should be -* fairly reasonable according to the vmalloc size, so it shouldn't be a -* big problem. -*/ +static struct vmap_block_queue * +addr_to_vbq (unsigned long addr) +{ +int cpu = (addr /VMAP_BLOCK_SIZE ) %num_possible_cpus (); +return &per_cpu (vmap_block_queue, cpu); +} -static unsigned long addr_to_vb_idx (unsigned long addr) +static unsigned long +addr_to_vb_va_start (unsigned long addr) { - addr -=VMALLOC_START & ~(VMAP_BLOCK_SIZE -1 ); - addr /=VMAP_BLOCK_SIZE ; -return addr; +/* Check if aligned. */ +if (IS_ALIGNED (addr,VMAP_BLOCK_SIZE )) +return addr; + +/* A start address of block an address belongs to. */ +return rounddown (addr,VMAP_BLOCK_SIZE ); }static void *vmap_block_vaddr (unsigned long va_start,unsigned long pages_off)
@@ -1953,7 +1951,7 @@ static void *vmap_block_vaddr(unsigned long va_start, unsigned long pages_off)unsigned long addr; addr = va_start + (pages_off <<PAGE_SHIFT ); -BUG_ON (addr_to_vb_idx (addr) !=addr_to_vb_idx (va_start)); +BUG_ON (addr_to_vb_va_start (addr) !=addr_to_vb_va_start (va_start));return (void *)addr; }
@@ -1970,7 +1968,6 @@ static void *new_vmap_block(unsigned int order, gfp_t gfp_mask)struct vmap_block_queue *vbq;struct vmap_block *vb;struct vmap_area *va; -unsigned long vb_idx;int node, err;void *vaddr;
@@ -2003,8 +2000,8 @@ static void *new_vmap_block(unsigned int order, gfp_t gfp_mask)bitmap_set (vb->used_map,0 , (1UL << order));INIT_LIST_HEAD (&vb->free_list); - vb_idx =addr_to_vb_idx (va->va_start); - err =xa_insert (&vmap_blocks, vb_idx, vb, gfp_mask); + vbq =addr_to_vbq (va->va_start); + err =xa_insert (&vbq->vmap_blocks, va->va_start, vb, gfp_mask);if (err) {kfree (vb);free_vmap_area (va);
@@ -2021,9 +2018,11 @@ static void *new_vmap_block(unsigned int order, gfp_t gfp_mask)static void free_vmap_block (struct vmap_block *vb) { +struct vmap_block_queue *vbq;struct vmap_block *tmp; - tmp =xa_erase (&vmap_blocks,addr_to_vb_idx (vb->va->va_start)); + vbq =addr_to_vbq (vb->va->va_start); + tmp =xa_erase (&vbq->vmap_blocks, vb->va->va_start);BUG_ON (tmp != vb);spin_lock (&vmap_area_lock);
@@ -2135,6 +2134,7 @@ static void vb_free(unsigned long addr, unsigned long size)unsigned long offset;unsigned int order;struct vmap_block *vb; +struct vmap_block_queue *vbq;BUG_ON (offset_in_page (size));BUG_ON (size >PAGE_SIZE *VMAP_MAX_ALLOC );
@@ -2143,7 +2143,10 @@ static void vb_free(unsigned long addr, unsigned long size) order =get_order (size); offset = (addr & (VMAP_BLOCK_SIZE -1 )) >>PAGE_SHIFT ; - vb =xa_load (&vmap_blocks,addr_to_vb_idx (addr)); + + vbq =addr_to_vbq (addr); + vb =xa_load (&vbq->vmap_blocks,addr_to_vb_va_start (addr)); +spin_lock (&vb->lock);bitmap_clear (vb->used_map, offset, (1UL << order));spin_unlock (&vb->lock);
@@ -3486,6 +3489,7 @@ static void vmap_ram_vread(char *buf, char *addr, int count, unsigned long flags {char *start;struct vmap_block *vb; +struct vmap_block_queue *vbq;unsigned long offset;unsigned int rs, re, n;
@@ -3503,7 +3507,8 @@ static void vmap_ram_vread(char *buf, char *addr, int count, unsigned long flags* Area is split into regions and tracked with vmap_block, read out * each region and zero fill the hole between regions. */ - vb =xa_load (&vmap_blocks,addr_to_vb_idx ((unsigned long )addr)); + vbq =addr_to_vbq ((unsigned long ) addr); + vb =xa_load (&vbq->vmap_blocks,addr_to_vb_va_start ((unsigned long ) addr));if (!vb) goto finished;
@@ -4272,6 +4277,7 @@ void __init vmalloc_init(void) p = &per_cpu (vfree_deferred, i);init_llist_head (&p->list);INIT_WORK (&p->wq, delayed_vfree_work); +xa_init (&vbq->vmap_blocks); }/* Import existing vmlist entries. */
Signature · 11 lines
2.30.2 <snip> Any thoughts patch? I do not consider it as a big improvement in performance. But, it tends to remove completely a contention on the "xa_lock" + it refactor slightly the per-cpu allocator. XFS workloads can be improved, though. -- Uladzislau Rezki
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
In reply to Uladzislau RezkiQuoted 13 lines · Uladzislau Rezki
Hello, Dave.
Quoted 11 lines
I'm travelling right now, but give me a few days and I'll test this against the XFS workloads that hammer the global vmalloc spin lock really, really badly. XFS can use
vm_map_ramand vmalloc really heavily for metadata buffers and hit the global spin lock from every CPU in the system at the same time (i.e. highly concurrent workloads). vmalloc is also heavily used in the hottest path throught the journal where we process and calculate delta changes to several million items every second, again spread across every CPU in the system at the same time.
Quoted 6 lines
Quoted 3 lines
We really need the global spinlock to go away completely, but in the mean time a shared read lock should help a little bit....
Could you please share some steps how to run your workloads in order to touch
vmalloc()code. I would like to have a look at it in more detail just for understanding the workloads.
Go search lore for the fsmark scalability benchmarks I've been running for the past 12-13 years on XFS. Essentially they are high concurency create/walk/modify/remove workloads on cold cache and limited memory configurations. Essentially it's hammering caches turning over inodes as fast as the system can possibly stream them in and out of memory....
Quoted 10 lines
<snip>
urezki@pc638:~/data/raid0/coding/linux-rcu.git/fs/xfs$ grep -rn vmalloc ./ ./xfs_log_priv.h:675: * Log vector and shadow buffers can be large, so we need to use kvmalloc() here ./xfs_log_priv.h:676: * to ensure success. Unfortunately, kvmalloc() only allows GFP_KERNEL contexts ./xfs_log_priv.h:677: * to fall back to vmalloc, so we can't actually do anything useful with gfp ./xfs_log_priv.h:678: * flags to control the kmalloc() behaviour within kvmalloc(). Hence kmalloc() ./xfs_log_priv.h:681: * vmalloc if it can't get somethign straight away from the free lists or ./xfs_log_priv.h:682: * buddy allocator. Hence we have to open code kvmalloc outselves here. ./xfs_log_priv.h:686: * allocations. This is actually the only way to make vmalloc() do GFP_NOFS ./xfs_log_priv.h:691:xlog_kvmalloc(
Did you read the comment above this function? I mean, it's all about how poorly kvmalloc() works for the highly concurrent, fail-fast context that occurs in the journal commit fast path, and how we open code it with kmalloc and vmalloc to work "ok" in this path.
Then if you go look at the commits related to it, you might find that XFS developers tend to write properly useful changelogs to document things like "it's better, but vmalloc will soon have lock contention problems if we hit it any harder"....
commit 8dc9384b7d75012856b02ff44c37566a55fc2abf
Author: Dave Chinner <[email protected]> Date: Tue Jan 4 17:22:18 2022 -0800
xfs: reduce kvmalloc overhead for CIL shadow buffers
Oh, let me count the ways that the kvmalloc API sucks dog eggs.
The problem is when we are logging lots of large objects, we hit kvmalloc really damn hard with costly order allocations, and behaviour utterly sucks:
- 49.73% xlog_cil_commit
- 31.62% kvmalloc_node
- 29.96% __kmalloc_node
- 29.38% kmalloc_large_node
- 29.33% __alloc_pages
- 24.33% __alloc_pages_slowpath.constprop.0
- 18.35% __alloc_pages_direct_compact
- 17.39% try_to_compact_pages
- compact_zone_order
- 15.26% compact_zone 5.29% __pageblock_pfn_to_page 3.71% PageHuge
- 1.44% isolate_migratepages_block 0.71% set_pfnblock_flags_mask
1.11% get_pfnblock_flags_mask
- 0.81% get_page_from_freelist
- 0.59% _raw_spin_lock_irqsave
- do_raw_spin_lock __pv_queued_spin_lock_slowpath
- 3.24% try_to_free_pages
- 3.14% shrink_node
- 2.94% shrink_slab.constprop.0
- 0.89% super_cache_count
- 0.66% xfs_fs_nr_cached_objects
- 0.65% xfs_reclaim_inodes_count 0.55% xfs_perag_get_tag
0.58% kfree_rcu_shrink_count
- 2.09% get_page_from_freelist
- 1.03% _raw_spin_lock_irqsave
- do_raw_spin_lock __pv_queued_spin_lock_slowpath
- 4.88% get_page_from_freelist
- 3.66% _raw_spin_lock_irqsave
- do_raw_spin_lock __pv_queued_spin_lock_slowpath
- 1.63% __vmalloc_node
- __vmalloc_node_range
- 1.10% __alloc_pages_bulk
- 0.93% __alloc_pages
- 0.92% get_page_from_freelist
- 0.89% rmqueue_bulk
- 0.69% _raw_spin_lock
- do_raw_spin_lock __pv_queued_spin_lock_slowpath
13.73% memcpy_erms
- 2.22% kvfree
On this workload, that's almost a dozen CPUs all trying to compact and reclaim memory inside kvmalloc_node at the same time. Yet it is regularly falling back to vmalloc despite all that compaction, page and shrinker reclaim that direct reclaim is doing. Copying all the metadata is taking far less CPU time than allocating the storage!
Direct reclaim should be considered extremely harmful.
This is a high frequency, high throughput, CPU usage and latency sensitive allocation. We've got memory there, and we're using kvmalloc to allow memory allocation to avoid doing lots of work to try to do contiguous allocations.
Except it still does *lots of costly work* that is unnecessary.
Worse: the only way to avoid the slowpath page allocation trying to do compaction on costly allocations is to turn off direct reclaim (i.e. remove __GFP_RECLAIM_DIRECT from the gfp flags).
Unfortunately, the stupid kvmalloc API then says "oh, this isn't a GFP_KERNEL allocation context, so you only get kmalloc!". This cuts off the vmalloc fallback, and this leads to almost instant OOM problems which ends up in filesystems deadlocks, shutdowns and/or kernel crashes.
I want some basic kvmalloc behaviour:
- kmalloc for a contiguous range with fail fast semantics - no compaction direct reclaim if the allocation enters the slow path.
- run normal vmalloc (i.e. GFP_KERNEL) if kmalloc fails
The really, really stupid part about this is these kvmalloc() calls are run under memalloc_nofs task context, so all the allocations are always reduced to GFP_NOFS regardless of the fact that kvmalloc requires GFP_KERNEL to be passed in. IOWs, we're already telling kvmalloc to behave differently to the gfp flags we pass in, but it still won't allow vmalloc to be run with anything other than GFP_KERNEL.
So, this patch open codes the kvmalloc() in the commit path to have the above described behaviour. The result is we more than halve the CPU time spend doing kvmalloc() in this path and transaction commits with 64kB objects in them more than doubles. i.e. we get ~5x reduction in CPU usage per costly-sized kvmalloc() invocation and the profile looks like this:
- 37.60% xlog_cil_commit
16.01% memcpy_erms
- 8.45% __kmalloc
- 8.04% kmalloc_order_trace
- 8.03% kmalloc_order
- 7.93% alloc_pages
- 7.90% __alloc_pages
- 4.05% __alloc_pages_slowpath.constprop.0
- 2.18% get_page_from_freelist
- 1.77% wake_all_kswapds
....
- __wake_up_common_lock
- 0.94% _raw_spin_lock_irqsave
- 3.72% get_page_from_freelist
- 2.43% _raw_spin_lock_irqsave
- 5.72% vmalloc
- 5.72% __vmalloc_node_range
- 4.81% __get_vm_area_node.constprop.0
- 3.26% alloc_vmap_area
- 2.52% _raw_spin_lock
- 1.46% _raw_spin_lock
0.56% __alloc_pages_bulk
- 4.66% kvfree
- 3.25% vfree
- __vfree
- 3.23% __vunmap
- 1.95% remove_vm_area
- 1.06% free_vmap_area_noflush
- 0.82% _raw_spin_lock
- 0.68% _raw_spin_lock
- 0.92% _raw_spin_lock
- 1.40% kfree
- 1.36% __free_pages
- 1.35% __free_pages_ok
- 1.02% _raw_spin_lock_irqsave
It's worth noting that over 50% of the CPU time spent allocating these shadow buffers is now spent on spinlocks. So the shadow buffer allocation overhead is greatly reduced by getting rid of direct reclaim from kmalloc, and could probably be made even less costly if vmalloc() didn't use global spinlocks to protect it's structures.
Signature · 2 lines
Dave Chinner [email protected]
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 9 lines · Dave Chinner
Did you read the comment above this function? I mean, it's all about how poorly
kvmalloc()works for the highly concurrent, fail-fast context that occurs in the journal commit fast path, and how we open code it with kmalloc and vmalloc to work "ok" in this path.Then if you go look at the commits related to it, you might find that XFS developers tend to write properly useful changelogs to document things like "it's better, but vmalloc will soon have lock contention problems if we hit it any harder"....
The problem with writing whinges like this is that mm developers don't read XFS changelogs. I certainly had no idea this was a problem, and I doubt anybody else who could make a start at fixing this problem had any idea either. Why go to all this effort instead of sending an email to linux-mm?
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 11 lines · Matthew Wilcox
Quoted 9 lines · Dave Chinner
Did you read the comment above this function? I mean, it's all about how poorly
kvmalloc()works for the highly concurrent, fail-fast context that occurs in the journal commit fast path, and how we open code it with kmalloc and vmalloc to work "ok" in this path.Then if you go look at the commits related to it, you might find that XFS developers tend to write properly useful changelogs to document things like "it's better, but vmalloc will soon have lock contention problems if we hit it any harder"....
Quoted 5 lines
The problem with writing whinges like this is that mm developers don't read XFS changelogs. I certainly had no idea this was a problem, and I doubt anybody else who could make a start at fixing this problem had any idea either. Why go to all this effort instead of sending an email to linux-mm?
<sigh>
If you read the mm/vmalloc.c change logs, you'd find that two weeks later, a bunch of commits went into the vmalloc code to change some of the stuff mentioned in the above XFS commit. That was a direct result of the discussion of vmalloc/kvmalloc inadequacies, and if you followed the links from these three commits:
30d3f01191d3 mm/vmalloc: be more explicit about supported gfp flags. 9376130c390a mm/vmalloc: add support for __GFP_NOFAIL 451769ebb7e7 mm/vmalloc: alloc GFP_NO{FS,IO} for vmalloc
You'd have found those discussions.
I went into great detail in that discussion about the problems with the kvmalloc/vmalloc APIs and the problems with actually using it in anger. e.g:
https://lore.kernel.org/all/[email protected]/T/#e8bc85de35d432dcbc35a16fc72b6a3daef2a0f78
In that discussion, I gave these examples and use cases about fail-fast for the kmalloc part of kvmalloc being needed, that arguments that GFP_NOFS didn't work with vmalloc were bullshit because we'd been using it heavily for years in GFP_NOFS contexts without issues, the lack of scope APIs for anything other NOFS/NOIO, that filesytsems want "retry forever" semantics, not the current __GFP_NOFAIL semantics that have all sorts of weird side effects, etc. I also point out that vmalloc is rapidly becoming one of the hottest paths in XFS in response to the comments that vmalloc "isn't a hot path".
Indeed, I point that the XFS change in that commit during that discussion, and you made exactly the same "you should raise this with mm developers" complaint then, too. Imagine how frsutrating it is when I was being told to raise vmalloc issues on the linux-mm list with mm developers during a discussion about vmalloc issues with mm developers on the linux-mm list. Especially as it wasn't just one mm developer that responded like that.
And yet, the 3 commits that came out of the discussion did nothing to change the actual problem we need to fix - fail-fast high-order kmalloc behaviour in kvmalloc() - and so the XFS commit still stands and is badly needed.
Repeatedly castigating people saying we should talk to mm developers rather than working around the API they maintain when we've repeatedly talked to the mm developers about getting changes made and repeatedly failed to get the changes we need made? Yeah, that leads to frustrations and commit messages documenting all the shit we haven't been able to get changed and so need to work around.....
-Dave.
Signature · 2 lines
Dave Chinner [email protected]
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
In reply to Dave ChinnerQuoted 23 lines
So, this patch open codes the
kvmalloc()in the commit path to have the above described behaviour. The result is we more than halve the CPU time spend doingkvmalloc()in this path and transaction commits with 64kB objects in them more than doubles. i.e. we get ~5x reduction in CPU usage per costly-sizedkvmalloc()invocation and the profile looks like this:- 37.60%
xlog_cil_commit
16.01%memcpy_erms
- 8.45%__kmalloc
- 8.04%kmalloc_order_trace
- 8.03%kmalloc_order
- 7.93%alloc_pages
- 7.90%__alloc_pages
- 4.05% __alloc_pages_slowpath.constprop.0
- 2.18%get_page_from_freelist
- 1.77%wake_all_kswapds
....
-__wake_up_common_lock
- 0.94%_raw_spin_lock_irqsave
- 3.72%get_page_from_freelist
- 2.43%_raw_spin_lock_irqsave
- 5.72% vmalloc
Quoted 8 lines
- 5.72%
__vmalloc_node_range
- 4.81% __get_vm_area_node.constprop.0
- 3.26%alloc_vmap_area
- 2.52%_raw_spin_lock
- 1.46%_raw_spin_lock
0.56%__alloc_pages_bulk
- 4.66% kvfree
- 3.25% vfree
OK, i see. I tried to use the fs_mark in different configurations. For example:
<snip>
time fs_mark -D 10000 -S0 -n 100000 -s 0 -L 32 -d ./scratch/0 -d ./scratch/1 -d ./scratch/2 \
-d ./scratch/3 -d ./scratch/4 -d ./scratch/5 -d ./scratch/6 -d ./scratch/7 -d ./scratch/8 \
-d ./scratch/9 -d ./scratch/10 -d ./scratch/11 -d ./scratch/12 -d ./scratch/13 \
-d ./scratch/14 -d ./scratch/15 -t 64 -F
<snip>
But i did not manage to trigger xlog_cil_commit() to fallback to vmalloc code. I think i should reduce an amount of memory on my kvm-pc and repeat the tests!
--
Uladzislau Rezki
Re: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 41 lines · Uladzislau Rezki
Quoted 23 lines
So, this patch open codes the
kvmalloc()in the commit path to have the above described behaviour. The result is we more than halve the CPU time spend doingkvmalloc()in this path and transaction commits with 64kB objects in them more than doubles. i.e. we get ~5x reduction in CPU usage per costly-sizedkvmalloc()invocation and the profile looks like this:- 37.60%
xlog_cil_commit
16.01%memcpy_erms
- 8.45%__kmalloc
- 8.04%kmalloc_order_trace
- 8.03%kmalloc_order
- 7.93%alloc_pages
- 7.90%__alloc_pages
- 4.05% __alloc_pages_slowpath.constprop.0
- 2.18%get_page_from_freelist
- 1.77%wake_all_kswapds
....
-__wake_up_common_lock
- 0.94%_raw_spin_lock_irqsave
- 3.72%get_page_from_freelist
- 2.43%_raw_spin_lock_irqsave
- 5.72% vmallocQuoted 8 lines
- 5.72%
__vmalloc_node_range
- 4.81% __get_vm_area_node.constprop.0
- 3.26%alloc_vmap_area
- 2.52%_raw_spin_lock
- 1.46%_raw_spin_lock
0.56%__alloc_pages_bulk
- 4.66% kvfree
- 3.25% vfreeOK, i see. I tried to use the
fs_markin different configurations. For example:<snip>
timefs_mark-D 10000 -S0 -n 100000 -s 0 -L 32 -d ./scratch/0 -d ./scratch/1 -d ./scratch/2 \(quoted diff) +0
-3-d ./scratch/3 -d ./scratch/4 -d ./scratch/5 -d ./scratch/6 -d ./scratch/7 -d ./scratch/8 \ -d ./scratch/9 -d ./scratch/10 -d ./scratch/11 -d ./scratch/12 -d ./scratch/13 \ -d ./scratch/14 -d ./scratch/15 -t 64 -F
Quoted 3 lines
But i did not manage to trigger
xlog_cil_commit()to fallback to vmalloc code. I think i should reduce an amount of memory on my kvm-pc and repeat the tests!
Simple way of doing is to use directory blocks that are larger than page size:
mkfs.xfs -n size=64k ....
We can hit that path in other ways - large attributes will hit it in the attr buffer allocation path, enabling the new attribute intent-based logging mode will hit it in the xlog_cil_commit path as well. IIRC, the above profile comes from the latter case, creating lots of zero length files with 64kB xattrs attached via fsmark.
-Dave.
Signature · 2 lines
Dave Chinner [email protected]
Uladzislau Rezki
maintainerRe: [PATCH v2 2/4] mm: vmalloc: use rwsem, mutex for vmap_area_lock and vmap_block->lock
Quoted 49 lines · Dave Chinner
Quoted 41 lines · Uladzislau Rezki
Quoted 23 lines
So, this patch open codes the
kvmalloc()in the commit path to have the above described behaviour. The result is we more than halve the CPU time spend doingkvmalloc()in this path and transaction commits with 64kB objects in them more than doubles. i.e. we get ~5x reduction in CPU usage per costly-sizedkvmalloc()invocation and the profile looks like this:- 37.60%
xlog_cil_commit
16.01%memcpy_erms
- 8.45%__kmalloc
- 8.04%kmalloc_order_trace
- 8.03%kmalloc_order
- 7.93%alloc_pages
- 7.90%__alloc_pages
- 4.05% __alloc_pages_slowpath.constprop.0
- 2.18%get_page_from_freelist
- 1.77%wake_all_kswapds
....
-__wake_up_common_lock
- 0.94%_raw_spin_lock_irqsave
- 3.72%get_page_from_freelist
- 2.43%_raw_spin_lock_irqsave
- 5.72% vmallocQuoted 8 lines
- 5.72%
__vmalloc_node_range
- 4.81% __get_vm_area_node.constprop.0
- 3.26%alloc_vmap_area
- 2.52%_raw_spin_lock
- 1.46%_raw_spin_lock
0.56%__alloc_pages_bulk
- 4.66% kvfree
- 3.25% vfreeOK, i see. I tried to use the
fs_markin different configurations. For example:<snip>
timefs_mark-D 10000 -S0 -n 100000 -s 0 -L 32 -d ./scratch/0 -d ./scratch/1 -d ./scratch/2 \(quoted diff) +0
-3-d ./scratch/3 -d ./scratch/4 -d ./scratch/5 -d ./scratch/6 -d ./scratch/7 -d ./scratch/8 \ -d ./scratch/9 -d ./scratch/10 -d ./scratch/11 -d ./scratch/12 -d ./scratch/13 \ -d ./scratch/14 -d ./scratch/15 -t 64 -FQuoted 3 lines
But i did not manage to trigger
xlog_cil_commit()to fallback to vmalloc code. I think i should reduce an amount of memory on my kvm-pc and repeat the tests!Simple way of doing is to use directory blocks that are larger than page size:
Quoted 8 lines
mkfs.xfs -n size=64k ....
We can hit that path in other ways - large attributes will hit it in the attr buffer allocation path, enabling the new attribute intent-based logging mode will hit it in the
xlog_cil_commitpath as well. IIRC, the above profile comes from the latter case, creating lots of zero length files with 64kB xattrs attached via fsmark.
Good. Thank you that is useful.
--
Uladzislau Rezki
PATCH v2 3/4 fs/proc/kcore: convert read_kcore() to read_kcore_iter()
In reply to Lorenzo StoakesNow we have eliminated spinlocks from the vread() case, convert read_kcore() to read_kcore_iter().
For the time being we still use a bounce buffer for vread(), however in the next patch we will convert this to interact directly with the iterator and eliminate the bounce buffer altogether.
--- fs/proc/kcore.c | 58 ++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 29 deletions(-)
fs/proc/kcore.c +29 -29
@@ -24,7 +24,7 @@#include <linux/memblock.h> #include <linux/init.h> #include <linux/slab.h> -#include <linux/uaccess.h> +#include <linux/uio.h> #include <asm/io.h> #include <linux/list.h> #include <linux/ioport.h>
@@ -308,9 +308,12 @@ static void append_kcore_note(char *notes, size_t *i, const char *name, }static ssize_t -read_kcore (struct file *file,char __user *buffer,size_t buflen,loff_t *fpos) +read_kcore_iter (struct kiocb *iocb,struct iov_iter *iter) { +struct file *file = iocb->ki_filp;char *buf = file->private_data; +loff_t *ppos = &iocb->ki_pos; +size_t phdrs_offset, notes_offset, data_offset;size_t page_offline_frozen =1 ;size_t phdrs_len, notes_len;
@@ -318,6 +321,7 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)size_t tsz;int nphdr;unsigned long start; +size_t buflen =iov_iter_count (iter);size_t orig_buflen = buflen;int ret =0 ;
@@ -333,7 +337,7 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) notes_offset = phdrs_offset + phdrs_len;/* ELF file header. */ -if (buflen && *fpos <sizeof (struct elfhdr )) { +if (buflen && *ppos <sizeof (struct elfhdr )) {struct elfhdr ehdr = { .e_ident = { [EI_MAG0 ] =ELFMAG0 ,
@@ -355,19 +359,18 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) .e_phnum = nphdr, }; - tsz =min_t (size_t, buflen,sizeof (struct elfhdr ) - *fpos); -if (copy_to_user (buffer, (char *)&ehdr + *fpos, tsz)) { + tsz =min_t (size_t, buflen,sizeof (struct elfhdr ) - *ppos); +if (copy_to_iter ((char *)&ehdr + *ppos, tsz, iter) != tsz) { ret = -EFAULT ; goto out; } - buffer += tsz; buflen -= tsz; - *fpos += tsz; + *ppos += tsz; }/* ELF program headers. */ -if (buflen && *fpos < phdrs_offset + phdrs_len) { +if (buflen && *ppos < phdrs_offset + phdrs_len) {struct elf_phdr *phdrs, *phdr; phdrs =kzalloc (phdrs_len,GFP_KERNEL );
@@ -397,22 +400,21 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) phdr++; } - tsz =min_t (size_t, buflen, phdrs_offset + phdrs_len - *fpos); -if (copy_to_user (buffer, (char *)phdrs + *fpos - phdrs_offset, - tsz)) { + tsz =min_t (size_t, buflen, phdrs_offset + phdrs_len - *ppos); +if (copy_to_iter ((char *)phdrs + *ppos - phdrs_offset, tsz, + iter) != tsz) {kfree (phdrs); ret = -EFAULT ; goto out; }kfree (phdrs); - buffer += tsz; buflen -= tsz; - *fpos += tsz; + *ppos += tsz; }/* ELF note segment. */ -if (buflen && *fpos < notes_offset + notes_len) { +if (buflen && *ppos < notes_offset + notes_len) {struct elf_prstatus prstatus = {};struct elf_prpsinfo prpsinfo = { .pr_sname ='R' ,
@@ -447,24 +449,23 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) vmcoreinfo_data, min(vmcoreinfo_size, notes_len - i)); - tsz =min_t (size_t, buflen, notes_offset + notes_len - *fpos); -if (copy_to_user (buffer, notes + *fpos - notes_offset, tsz)) { + tsz =min_t (size_t, buflen, notes_offset + notes_len - *ppos); +if (copy_to_iter (notes + *ppos - notes_offset, tsz, iter) != tsz) {kfree (notes); ret = -EFAULT ; goto out; }kfree (notes); - buffer += tsz; buflen -= tsz; - *fpos += tsz; + *ppos += tsz; }/* * Check to see if our file offset matches with any of * the addresses in the elf_phdr on our list. */ - start =kc_offset_to_vaddr (*fpos - data_offset); + start =kc_offset_to_vaddr (*ppos - data_offset);if ((tsz = (PAGE_SIZE - (start & ~PAGE_MASK ))) > buflen) tsz = buflen;
@@ -497,7 +498,7 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) }if (!m) { -if (clear_user (buffer, tsz)) { +if (iov_iter_zero (tsz, iter) != tsz) { ret = -EFAULT ; goto out; }
@@ -508,14 +509,14 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)case KCORE_VMALLOC :vread (buf, (char *)start, tsz);/* we have to zero-fill user buffer even if no read */ -if (copy_to_user (buffer, buf, tsz)) { +if (copy_to_iter (buf, tsz, iter) != tsz) { ret = -EFAULT ; goto out; }break ;case KCORE_USER :/* User page is handled prior to normal kernel page: */ -if (copy_to_user (buffer, (char *)start, tsz)) { +if (copy_to_iter ((char *)start, tsz, iter) != tsz) { ret = -EFAULT ; goto out; }
@@ -531,7 +532,7 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)*/ if (!page ||PageOffline (page) ||is_page_hwpoison (page) || !pfn_is_ram (pfn)) { -if (clear_user (buffer, tsz)) { +if (iov_iter_zero (tsz, iter) != tsz) { ret = -EFAULT ; goto out; }
@@ -541,25 +542,24 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)case KCORE_VMEMMAP :case KCORE_TEXT :/* -* We use _copy_to_user() to bypass usermode hardening +* We use _copy_to_iter() to bypass usermode hardening * which would otherwise prevent this operation. */ -if (_copy_to_user (buffer, (char *)start, tsz)) { +if (_copy_to_iter ((char *)start, tsz, iter) != tsz) { ret = -EFAULT ; goto out; }break ;default :pr_warn_once ("Unhandled KCORE type: %d\n" , m->type); -if (clear_user (buffer, tsz)) { +if (iov_iter_zero (tsz, iter) != tsz) { ret = -EFAULT ; goto out; } } skip: buflen -= tsz; - *fpos += tsz; - buffer += tsz; + *ppos += tsz; start += tsz; tsz = (buflen >PAGE_SIZE ?PAGE_SIZE : buflen); }
@@ -603,7 +603,7 @@ static int release_kcore(struct inode *inode, struct file *file) }static const struct proc_ops kcore_proc_ops = { - .proc_read = read_kcore, + .proc_read_iter = read_kcore_iter, .proc_open = open_kcore, .proc_release = release_kcore, .proc_lseek = default_llseek,
Signature · 1 line
2.39.2
PATCH v2 4/4 mm: vmalloc: convert vread() to vread_iter()
In reply to Lorenzo StoakesHaving previously laid the foundation for converting vread() to an iterator function, pull the trigger and do so.
This patch attempts to provide minimal refactoring and to reflect the existing logic as best we can, with the exception of aligned_vread_iter() which drops the use of the deprecated kmap_atomic() in favour of kmap_local_page().
All existing logic to zero portions of memory not read remain and there should be no functional difference other than a performance improvement in /proc/kcore access to vmalloc regions.
Now we have discarded with the need for a bounce buffer at all in read_kcore_iter(), we dispense with the one allocated there altogether.
--- fs/proc/kcore.c | 21 +-------- include/linux/vmalloc.h | 3 +- mm/nommu.c | 10 ++-- mm/vmalloc.c | 101 +++++++++++++++++++++------------------- 4 files changed, 62 insertions(+), 73 deletions(-)
fs/proc/kcore.c +2 -19
@@ -307,13 +307,9 @@ static void append_kcore_note(char *notes, size_t *i, const char *name, *i =ALIGN (*i + descsz,4 ); } -static ssize_t -read_kcore_iter (struct kiocb *iocb,struct iov_iter *iter) +static ssize_t read_kcore_iter (struct kiocb *iocb,struct iov_iter *iter) { -struct file *file = iocb->ki_filp; -char *buf = file->private_data;loff_t *ppos = &iocb->ki_pos; -size_t phdrs_offset, notes_offset, data_offset;size_t page_offline_frozen =1 ;size_t phdrs_len, notes_len;
@@ -507,9 +503,7 @@ read_kcore_iter(struct kiocb *iocb, struct iov_iter *iter)switch (m->type) {case KCORE_VMALLOC : -vread (buf, (char *)start, tsz); -/* we have to zero-fill user buffer even if no read */ -if (copy_to_iter (buf, tsz, iter) != tsz) { +if (vread_iter (iter, (char *)start, tsz) != tsz) { ret = -EFAULT ; goto out; }
@@ -582,10 +576,6 @@ static int open_kcore(struct inode *inode, struct file *filp)if (ret)return ret; - filp->private_data =kmalloc (PAGE_SIZE ,GFP_KERNEL ); -if (!filp->private_data) -return -ENOMEM ; -if (kcore_need_update)kcore_update_ram ();if (i_size_read (inode) != proc_root_kcore->size) {
@@ -596,16 +586,9 @@ static int open_kcore(struct inode *inode, struct file *filp)return 0 ; } -static int release_kcore (struct inode *inode,struct file *file) -{ -kfree (file->private_data); -return 0 ; -} -static const struct proc_ops kcore_proc_ops = { .proc_read_iter = read_kcore_iter, .proc_open = open_kcore, - .proc_release = release_kcore, .proc_lseek = default_llseek, };
include/linux/vmalloc.h +2 -1
@@ -9,6 +9,7 @@#include <asm/page.h> /* pgprot_t */ #include <linux/rbtree.h> #include <linux/overflow.h> +#include <linux/uio.h> #include <asm/vmalloc.h>
@@ -251,7 +252,7 @@ static inline void set_vm_flush_reset_perms(void *addr)#endif /* for /proc/kcore */ -extern long vread (char *buf,char *addr,unsigned long count); +extern long vread_iter (struct iov_iter *iter,char *addr,size_t count);/* * Internals. Don't use..
mm/nommu.c +5 -5
@@ -36,6 +36,7 @@#include <linux/printk.h> #include <linux/uaccess.h> +#include <linux/uio.h> #include <asm/tlb.h> #include <asm/tlbflush.h> #include <asm/mmu_context.h>
@@ -198,14 +199,13 @@ unsigned long vmalloc_to_pfn(const void *addr) }EXPORT_SYMBOL (vmalloc_to_pfn); -long vread (char *buf,char *addr,unsigned long count) +long vread_iter (struct iov_iter *iter,char *addr,size_t count) {/* Don't allow overflow */ -if ((unsigned long ) buf + count < count) - count = -(unsigned long ) buf; +if ((unsigned long ) addr + count < count) + count = -(unsigned long ) addr; -memcpy (buf, addr, count); -return count; +return copy_to_iter (addr, count, iter); }/*
mm/vmalloc.c +53 -48
203 lines not on this page · raw message
Signature · 1 line
2.39.2