rwsem: performance optimizations
A message that is not in the archive (sent off-list or not yet received): [email protected]
v7 cover rwsem: performance optimizations
We fixed various style issues for version 7 of this patchset and added brief explanations of the MCS lock code that we put in a separate file. We will like to have it merged if there are no objections.
In this patchset, we introduce two categories of optimizations to read write semaphore. The first four patches from Alex Shi reduce cache bouncing of the sem->count field by doing a pre-read of the sem->count and avoid cmpxchg if possible.
The last two patches introduce similar optimistic spinning logic as the mutex code for the writer lock acquisition of rwsem. This addresses the general 'mutexes out perform writer-rwsems' situations that has been seen in more than one case. Users now need not worry about performance issues when choosing between these two locking mechanisms.
Without these optimizations, Davidlohr Bueso saw a -8% regression to aim7's shared and high_systime workloads when he switched i_mmap_mutex to rwsem. Tests were on 8 socket 80 cores system. With the patchset, he got significant improvements to the aim7 suite instead of regressions:
alltests (+16.3%), custom (+20%), disk (+19.5%), high_systime (+7%), shared (+18.4%) and short (+6.3%).
Tim Chen also got a +5% improvements to exim mail server workload on a 40 core system.
Thanks to Ingo Molnar, Peter Hurley and Peter Zijlstra for reviewing this patchset.
Regards,
Tim Chen
Changelog:
v7:
1. Rename mcslock.h to mcs_spinlock.h and also rename mcs related fields with mcs prefix.
2. Properly define type of *mcs_lock field instead of leaving it as *void.
3. Added breif explanation of mcs lock.
v6:
1. Fix missing mcslock.h file.
2. Fix various code style issues.
v5:
1. Try optimistic spinning before we put the writer on the wait queue to avoid bottlenecking at wait queue. This provides 5% boost to exim workload and between 2% to 8% boost to aim7.
2. Put MCS locking code into its own mcslock.h file for better reuse between mutex.c and rwsem.c
3. Remove the configuration RWSEM_SPIN_ON_WRITE_OWNER and make the operations default per Ingo's suggestions.
v4:
1. Fixed a bug in task_struct definition in rwsem_can_spin_on_owner
2. Fix another typo for RWSEM_SPIN_ON_WRITE_OWNER config option
v3:
1. Added ACCESS_ONCE to sem->count access in rwsem_can_spin_on_owner.
2. Fix typo bug for RWSEM_SPIN_ON_WRITE_OWNER option in init/Kconfig
v2:
1. Reorganize changes to down_write_trylock and do_wake into 4 patches and fixed a bug referencing &sem->count when sem->count is intended.
2. Fix unsafe sem->owner de-reference in rwsem_can_spin_on_owner.
the option to be on for more seasoning but can be turned off should it be detrimental.
3. Various patch comments update
Alex Shi (4):
rwsem: check the lock before cpmxchg in down_write_trylock
rwsem: remove 'out' label in do_wake
rwsem: remove try_reader_grant label do_wake rwsem/wake: check lock before do atomic update
Tim Chen (2):
MCS Lock: Restructure the MCS lock defines and locking code into its
own file
rwsem: do optimistic spinning for writer lock acquisition
include/asm-generic/rwsem.h | 8 +- include/linux/mcs_spinlock.h | 64 ++++++++++++ include/linux/mutex.h | 5 +- include/linux/rwsem.h | 7 +- kernel/mutex.c | 60 ++---------- kernel/rwsem.c | 19 ++++- lib/rwsem.c | 226 ++++++++++++++++++++++++++++++++++++----- 7 files changed, 300 insertions(+), 89 deletions(-) create mode 100644 include/linux/mcs_spinlock.h
Signature · 8 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v7 1/6 rwsem: check the lock before cpmxchg in down_write_trylock
In reply to a message not in the archiveCmpxchg will cause the cacheline bouning when do the value checking, that cause scalability issue in a large machine (like a 80 core box).
So a lock pre-read can relief this contention.
--- include/asm-generic/rwsem.h | 8 ++++---- 1 files changed, 4 insertions(+), 4 deletions(-)
include/asm-generic/rwsem.h +4 -4
@@ -70,11 +70,11 @@ static inline void __down_write(struct rw_semaphore *sem)static inline int __down_write_trylock (struct rw_semaphore *sem) { -long tmp; +if (unlikely (sem->count !=RWSEM_UNLOCKED_VALUE )) +return 0 ; - tmp =cmpxchg (&sem->count,RWSEM_UNLOCKED_VALUE , -RWSEM_ACTIVE_WRITE_BIAS ); -return tmp ==RWSEM_UNLOCKED_VALUE ; +return cmpxchg (&sem->count,RWSEM_UNLOCKED_VALUE , +RWSEM_ACTIVE_WRITE_BIAS ) ==RWSEM_UNLOCKED_VALUE ; }/*
Signature · 9 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v7 2/6 rwsem: remove 'out' label in do_wake
In reply to a message not in the archiveThat make code simple and more readable.
--- lib/rwsem.c | 5 ++--- 1 files changed, 2 insertions(+), 3 deletions(-)
lib/rwsem.c +2 -3
@@ -75,7 +75,7 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)* will block as they will notice the queued writer. */ wake_up_process (waiter->task); - goto out; +return sem; }/* Writers might steal the lock before we grant it to the next reader.
@@ -91,7 +91,7 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)/* A writer stole the lock. Undo our reader grant. */ if (rwsem_atomic_update (-adjustment, sem) &RWSEM_ACTIVE_MASK ) - goto out; +return sem;/* Last active locker left. Retry waking readers. */ goto try_reader_grant; }
@@ -136,7 +136,6 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type) sem->wait_list.next = next; next->prev = &sem->wait_list; - out:return sem; }
Signature · 9 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v7 3/6 rwsem: remove try_reader_grant label do_wake
In reply to a message not in the archiveThat make code simple and more readable
--- lib/rwsem.c | 12 +++++++----- 1 files changed, 7 insertions(+), 5 deletions(-)
lib/rwsem.c +7 -5
@@ -85,15 +85,17 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type) adjustment =0 ;if (wake_type !=RWSEM_WAKE_READ_OWNED ) { adjustment =RWSEM_ACTIVE_READ_BIAS ; - try_reader_grant: - oldcount =rwsem_atomic_update (adjustment, sem) - adjustment; -if (unlikely (oldcount <RWSEM_WAITING_BIAS )) { -/* A writer stole the lock. Undo our reader grant. */ +while (1 ) { + oldcount =rwsem_atomic_update (adjustment, sem) + - adjustment; +if (likely (oldcount >=RWSEM_WAITING_BIAS )) +break ; + +/* A writer stole the lock. Undo our reader grant. */ if (rwsem_atomic_update (-adjustment, sem) &RWSEM_ACTIVE_MASK )return sem;/* Last active locker left. Retry waking readers. */ - goto try_reader_grant; } }
Signature · 9 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v7 4/6 rwsem/wake: check lock before do atomic update
In reply to a message not in the archiveAtomic update lock and roll back will cause cache bouncing in large machine. A lock status pre-read can relieve this problem
--- lib/rwsem.c | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-)
lib/rwsem.c +7 -1
@@ -64,7 +64,7 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)struct rwsem_waiter *waiter;struct task_struct *tsk;struct list_head *next; -long oldcount, woken, loop, adjustment; +long woken, loop, adjustment; waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);if (waiter->type ==RWSEM_WAITING_FOR_WRITE ) {
@@ -86,6 +86,12 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)if (wake_type !=RWSEM_WAKE_READ_OWNED ) { adjustment =RWSEM_ACTIVE_READ_BIAS ;while (1 ) { +long oldcount; + +/* A writer stole the lock. */ +if (unlikely (sem->count <RWSEM_WAITING_BIAS )) +return sem; + oldcount =rwsem_atomic_update (adjustment, sem) - adjustment;if (likely (oldcount >=RWSEM_WAITING_BIAS ))
Signature · 9 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v7 5/6 MCS Lock: Restructure the MCS lock defines and locking code into its own file
In reply to a message not in the archiveWe will need the MCS lock code for doing optimistic spinning for rwsem. Extracting the MCS code from mutex.c and put into its own file allow us to reuse this code easily for rwsem.
--- include/linux/mcs_spinlock.h | 64 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mutex.h | 5 ++- kernel/mutex.c | 60 ++++---------------------------------- 3 files changed, 74 insertions(+), 55 deletions(-) create mode 100644 include/linux/mcs_spinlock.h
include/linux/mcs_spinlock.h new file +64 -0
@@ -0,0 +1,64 @@ +/* +* MCS lock defines +* +* This file contains the main data structure and API definitions of MCS lock. +* +* The MCS lock (proposed by Mellor-Crummey and Scott) is a simple spin-lock +* with the desirable properties of being fair, and with each cpu trying +* to acquire the lock spinning on a local variable. +* It avoids expensive cache bouncings that common test-and-set spin-lock +* implementations incur. +*/ +#ifndef __LINUX_MCS_SPINLOCK_H +#define __LINUX_MCS_SPINLOCK_H + +struct mcs_spinlock { +struct mcs_spinlock *next; +int locked;/* 1 if lock acquired */ +}; + +/* +* We don't inline mcs_spin_lock() so that perf can correctly account for the +* time spent in this lock function. +*/ +static noinline +void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node) +{ +struct mcs_spinlock *prev; + +/* Init node */ + node->locked =0 ; + node->next =NULL ; + + prev =xchg (lock, node); +if (likely (prev ==NULL )) { +/* Lock acquired */ + node->locked =1 ; +return ; + } +ACCESS_ONCE (prev->next) = node; +smp_wmb (); +/* Wait until the lock holder passes the lock down */ +while (!ACCESS_ONCE (node->locked)) +arch_mutex_cpu_relax (); +} + +static void mcs_spin_unlock (struct mcs_spinlock **lock,struct mcs_spinlock *node) +{ +struct mcs_spinlock *next =ACCESS_ONCE (node->next); + +if (likely (!next)) { +/* +* Release the lock by setting it to NULL +*/ +if (cmpxchg (lock, node,NULL ) == node) +return ; +/* Wait until the next pointer is set */ +while (!(next =ACCESS_ONCE (node->next))) +arch_mutex_cpu_relax (); + } +ACCESS_ONCE (next->locked) =1 ; +smp_wmb (); +} + +#endif /* __LINUX_MCS_SPINLOCK_H */
include/linux/mutex.h +3 -2
@@ -46,6 +46,7 @@* - detects multi-task circular deadlocks and prints out all affected * locks and tasks (and only those tasks) */ +struct mcs_spinlock ;struct mutex {/* 1: unlocked, 0: locked, negative: locked, possible waiters */ atomic_t count;
@@ -55,7 +56,7 @@ struct mutex {struct task_struct *owner;#endif #ifdef CONFIG_MUTEX_SPIN_ON_OWNER -void *spin_mlock;/* Spinner MCS lock */ +struct mcs_spinlock *mcs_lock;/* Spinner MCS lock */ #endif #ifdef CONFIG_DEBUG_MUTEXES const char *name;
@@ -179,4 +180,4 @@ extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);#define arch_mutex_cpu_relax () cpu_relax()#endif -#endif +#endif /* __LINUX_MUTEX_H */
kernel/mutex.c +7 -53
102 lines not on this page · raw message
Signature · 9 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH v7 6/6 rwsem: do optimistic spinning for writer lock acquisition
In reply to a message not in the archiveWe want to add optimistic spinning to rwsems because
the writer rwsem does not perform as well as mutexes. Tim noticed that for exim (mail server) workloads, when reverting commit 4fc3f1d6 and Davidlohr noticed it when converting the i_mmap_mutex to a rwsem in some aim7 workloads. We've noticed that the biggest difference
is when we fail to acquire a mutex in the fastpath, optimistic spinning comes in to play and we can avoid a large amount of unnecessary sleeping and overhead of moving tasks in and out of wait queue.
Allowing optimistic spinning before putting the writer on the wait queue reduces wait queue contention and provided greater chance for the rwsem to get acquired. With these changes, rwsem is on par with mutex.
--- include/linux/rwsem.h | 7 ++- kernel/rwsem.c | 19 +++++- lib/rwsem.c | 201 ++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 206 insertions(+), 21 deletions(-)
include/linux/rwsem.h +6 -1
@@ -22,10 +22,13 @@ struct rw_semaphore; #include <linux/rwsem-spinlock.h> /* use a generic implementation */ #else /* All arch specific implementations share the same struct */ +struct mcs_spinlock; struct rw_semaphore { long count; raw_spinlock_t wait_lock; struct list_head wait_list; + struct task_struct *owner; /* write owner */ + struct mcs_spinlock *mcs_lock; #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif
@@ -58,7 +61,9 @@ static inline int rwsem_is_locked(struct rw_semaphore *sem)#define __RWSEM_INITIALIZER (name) \ { RWSEM_UNLOCKED_VALUE, \ __RAW_SPIN_LOCK_UNLOCKED(name.wait_lock), \ - LIST_HEAD_INIT((name).wait_list) \ + LIST_HEAD_INIT((name).wait_list), \ + NULL, \ + NULL \ __RWSEM_DEP_MAP_INIT(name) }#define DECLARE_RWSEM (name) \
kernel/rwsem.c +18 -1
@@ -12,6 +12,16 @@#include <linux/atomic.h> +static inline void rwsem_set_owner (struct rw_semaphore *sem) +{ + sem->owner = current; +} + +static inline void rwsem_clear_owner (struct rw_semaphore *sem) +{ + sem->owner =NULL ; +} +/* * lock for reading */
@@ -48,6 +58,7 @@ void __sched down_write(struct rw_semaphore *sem)rwsem_acquire (&sem->dep_map,0 ,0 , _RET_IP_);LOCK_CONTENDED (sem, __down_write_trylock, __down_write); +rwsem_set_owner (sem); }EXPORT_SYMBOL (down_write);
@@ -59,8 +70,10 @@ int down_write_trylock(struct rw_semaphore *sem) {int ret =__down_write_trylock (sem); -if (ret ==1 ) +if (ret ==1 ) {rwsem_acquire (&sem->dep_map,0 ,1 , _RET_IP_); +rwsem_set_owner (sem); + }return ret; }
@@ -86,6 +99,7 @@ void up_write(struct rw_semaphore *sem)rwsem_release (&sem->dep_map,1 , _RET_IP_);__up_write (sem); +rwsem_clear_owner (sem); }EXPORT_SYMBOL (up_write);
@@ -100,6 +114,7 @@ void downgrade_write(struct rw_semaphore *sem)* dependency. */ __downgrade_write (sem); +rwsem_clear_owner (sem); }EXPORT_SYMBOL (downgrade_write);
@@ -122,6 +137,7 @@ void _down_write_nest_lock(struct rw_semaphore *sem, struct lockdep_map *nest)rwsem_acquire_nest (&sem->dep_map,0 ,0 , nest, _RET_IP_);LOCK_CONTENDED (sem, __down_write_trylock, __down_write); +rwsem_set_owner (sem); }EXPORT_SYMBOL (_down_write_nest_lock);
@@ -141,6 +157,7 @@ void down_write_nested(struct rw_semaphore *sem, int subclass)rwsem_acquire (&sem->dep_map, subclass,0 , _RET_IP_);LOCK_CONTENDED (sem, __down_write_trylock, __down_write); +rwsem_set_owner (sem); }EXPORT_SYMBOL (down_write_nested);
lib/rwsem.c +182 -19
247 lines not on this page · raw message
Signature · 8 lines
1.7.4.4 -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>