Skip to content

Commit 2e30254

Browse files
ftang1torvalds
authored andcommitted
mm: relocate 'write_protect_seq' in struct mm_struct
0day robot reported a 9.2% regression for will-it-scale mmap1 test case[1], caused by commit 57efa1f ("mm/gup: prevent gup_fast from racing with COW during fork"). Further debug shows the regression is due to that commit changes the offset of hot fields 'mmap_lock' inside structure 'mm_struct', thus some cache alignment changes. From the perf data, the contention for 'mmap_lock' is very severe and takes around 95% cpu cycles, and it is a rw_semaphore struct rw_semaphore { atomic_long_t count; /* 8 bytes */ atomic_long_t owner; /* 8 bytes */ struct optimistic_spin_queue osq; /* spinner MCS lock */ ... Before commit 57efa1f adds the 'write_protect_seq', it happens to have a very optimal cache alignment layout, as Linus explained: "and before the addition of the 'write_protect_seq' field, the mmap_sem was at offset 120 in 'struct mm_struct'. Which meant that count and owner were in two different cachelines, and then when you have contention and spend time in rwsem_down_write_slowpath(), this is probably *exactly* the kind of layout you want. Because first the rwsem_write_trylock() will do a cmpxchg on the first cacheline (for the optimistic fast-path), and then in the case of contention, rwsem_down_write_slowpath() will just access the second cacheline. Which is probably just optimal for a load that spends a lot of time contended - new waiters touch that first cacheline, and then they queue themselves up on the second cacheline." After the commit, the rw_semaphore is at offset 128, which means the 'count' and 'owner' fields are now in the same cacheline, and causes more cache bouncing. Currently there are 3 "#ifdef CONFIG_XXX" before 'mmap_lock' which will affect its offset: CONFIG_MMU CONFIG_MEMBARRIER CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES The layout above is on 64 bits system with 0day's default kernel config (similar to RHEL-8.3's config), in which all these 3 options are 'y'. And the layout can vary with different kernel configs. Relayouting a structure is usually a double-edged sword, as sometimes it can helps one case, but hurt other cases. For this case, one solution is, as the newly added 'write_protect_seq' is a 4 bytes long seqcount_t (when CONFIG_DEBUG_LOCK_ALLOC=n), placing it into an existing 4 bytes hole in 'mm_struct' will not change other fields' alignment, while restoring the regression. Link: https://lore.kernel.org/lkml/20210525031636.GB7744@xsang-OptiPlex-9020/ [1] Reported-by: kernel test robot <[email protected]> Signed-off-by: Feng Tang <[email protected]> Reviewed-by: John Hubbard <[email protected]> Reviewed-by: Jason Gunthorpe <[email protected]> Cc: Peter Xu <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 43cb5d4 commit 2e30254

File tree

1 file changed

+20
-7
lines changed

1 file changed

+20
-7
lines changed

include/linux/mm_types.h

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -445,13 +445,6 @@ struct mm_struct {
445445
*/
446446
atomic_t has_pinned;
447447

448-
/**
449-
* @write_protect_seq: Locked when any thread is write
450-
* protecting pages mapped by this mm to enforce a later COW,
451-
* for instance during page table copying for fork().
452-
*/
453-
seqcount_t write_protect_seq;
454-
455448
#ifdef CONFIG_MMU
456449
atomic_long_t pgtables_bytes; /* PTE page table pages */
457450
#endif
@@ -460,6 +453,18 @@ struct mm_struct {
460453
spinlock_t page_table_lock; /* Protects page tables and some
461454
* counters
462455
*/
456+
/*
457+
* With some kernel config, the current mmap_lock's offset
458+
* inside 'mm_struct' is at 0x120, which is very optimal, as
459+
* its two hot fields 'count' and 'owner' sit in 2 different
460+
* cachelines, and when mmap_lock is highly contended, both
461+
* of the 2 fields will be accessed frequently, current layout
462+
* will help to reduce cache bouncing.
463+
*
464+
* So please be careful with adding new fields before
465+
* mmap_lock, which can easily push the 2 fields into one
466+
* cacheline.
467+
*/
463468
struct rw_semaphore mmap_lock;
464469

465470
struct list_head mmlist; /* List of maybe swapped mm's. These
@@ -480,7 +485,15 @@ struct mm_struct {
480485
unsigned long stack_vm; /* VM_STACK */
481486
unsigned long def_flags;
482487

488+
/**
489+
* @write_protect_seq: Locked when any thread is write
490+
* protecting pages mapped by this mm to enforce a later COW,
491+
* for instance during page table copying for fork().
492+
*/
493+
seqcount_t write_protect_seq;
494+
483495
spinlock_t arg_lock; /* protect the below fields */
496+
484497
unsigned long start_code, end_code, start_data, end_data;
485498
unsigned long start_brk, brk, start_stack;
486499
unsigned long arg_start, arg_end, env_start, env_end;

0 commit comments

Comments
 (0)