History

locking/rwsem: Adaptive disabling of reader optimistic spinning

Reader optimistic spinning is helpful when the reader critical section is short and there aren't that many readers around. It makes readers relatively more preferred than writers. When a writer times out spinning on a reader-owned lock and set the nospinnable bits, there are two main reasons for that. 1) The reader critical section is long, perhaps the task sleeps after acquiring the read lock. 2) There are just too many readers contending the lock causing it to take a while to service all of them. In the former case, long reader critical section will impede the progress of writers which is usually more important for system performance. In the later case, reader optimistic spinning tends to make the reader groups that contain readers that acquire the lock together smaller leading to more of them. That may hurt performance in some cases. In other words, the setting of nonspinnable bits indicates that reader optimistic spinning may not be helpful for those workloads that cause it. Therefore, any writers that have observed the setting of the writer nonspinnable bit for a given rwsem after they fail to acquire the lock via optimistic spinning will set the reader nonspinnable bit once they acquire the write lock. Similarly, readers that observe the setting of reader nonspinnable bit at slowpath entry will also set the reader nonspinnable bit when they acquire the read lock via the wakeup path. Once the reader nonspinnable bit is on, it will only be reset when a writer is able to acquire the rwsem in the fast path or somehow a reader or writer in the slowpath doesn't observe the nonspinable bit. This is to discourage reader optmistic spinning on that particular rwsem and make writers more preferred. This adaptive disabling of reader optimistic spinning will alleviate some of the negative side effect of this feature. In addition, this patch tries to make readers in the spinning queue follow the phase-fair principle after quitting optimistic spinning by checking if another reader has somehow acquired a read lock after this reader enters the optimistic spinning queue. If so and the rwsem is still reader-owned, this reader is in the right read-phase and can attempt to acquire the lock. On a 2-socket 40-core 80-thread Skylake system, the page_fault1 test of the will-it-scale benchmark was run with various number of threads. The number of operations done before reader optimistic spinning patches, this patch and after this patch were: Threads Before rspin Before patch After patch %change ------- ------------ ------------ ----------- ------- 20 5541068 5345484 5455667 -3.5%/ +2.1% 40 10185150 7292313 9219276 -28.5%/+26.4% 60 8196733 6460517 7181209 -21.2%/+11.2% 80 9508864 6739559 8107025 -29.1%/+20.3% This patch doesn't recover all the lost performance, but it is more than half. Given the fact that reader optimistic spinning does benefit some workloads, this is a good compromise. Using the rwsem locking microbenchmark with very short critical section, this patch doesn't have too much impact on locking performance as shown by the locking rates (kops/s) below with equal numbers of readers and writers before and after this patch: # of Threads Pre-patch Post-patch ------------ --------- ---------- 2 4,730 4,969 4 4,814 4,786 8 4,866 4,815 16 4,715 4,511 32 3,338 3,500 64 3,212 3,389 80 3,110 3,044 When running the locking microbenchmark with 40 dedicated reader and writer threads, however, the reader performance is curtailed to favor the writer. Before patch: 40 readers, Iterations Min/Mean/Max = 204,026/234,309/254,816 40 writers, Iterations Min/Mean/Max = 88,515/95,884/115,644 After patch: 40 readers, Iterations Min/Mean/Max = 33,813/35,260/36,791 40 writers, Iterations Min/Mean/Max = 95,368/96,565/97,798

Waiman Long committed 7 years ago

Commit 5cfd92e1 parent 7d43f1c2 files · +135 −8Browse files

Showing the changes to kernel/locking (2 of 2 files). Show all

Unified · Split

2 files
  1. kernel/locking/lock_events_list.h +6 −4
  2. kernel/locking/rwsem.c +129 −4
kernel/locking/lock_events_list.h+6−4
@@ -56,10 +56,12 @@ LOCK_EVENT(rwsem_sleep_reader) /* # of reader sleeps */ · 55 unmodified lines
56LOCK_EVENT(rwsem_sleep_writer) /* # of writer sleeps */56LOCK_EVENT(rwsem_sleep_writer) /* # of writer sleeps */
57LOCK_EVENT(rwsem_wake_reader) /* # of reader wakeups */57LOCK_EVENT(rwsem_wake_reader) /* # of reader wakeups */
58LOCK_EVENT(rwsem_wake_writer) /* # of writer wakeups */58LOCK_EVENT(rwsem_wake_writer) /* # of writer wakeups */
59LOCK_EVENT(rwsem_opt_rlock) /* # of read locks opt-spin acquired */59LOCK_EVENT(rwsem_opt_rlock) /* # of opt-acquired read locks */
60LOCK_EVENT(rwsem_opt_wlock) /* # of write locks opt-spin acquired */60LOCK_EVENT(rwsem_opt_wlock) /* # of opt-acquired write locks */
61LOCK_EVENT(rwsem_opt_fail) /* # of failed opt-spinnings */61LOCK_EVENT(rwsem_opt_fail) /* # of failed optspins */
62LOCK_EVENT(rwsem_opt_nospin) /* # of disabled reader opt-spinnings */62LOCK_EVENT(rwsem_opt_nospin) /* # of disabled optspins */
63LOCK_EVENT(rwsem_opt_norspin) /* # of disabled reader-only optspins */
64LOCK_EVENT(rwsem_opt_rlock2) /* # of opt-acquired 2ndary read locks */
63LOCK_EVENT(rwsem_rlock) /* # of read locks acquired */65LOCK_EVENT(rwsem_rlock) /* # of read locks acquired */
64LOCK_EVENT(rwsem_rlock_fast) /* # of fast read locks acquired */66LOCK_EVENT(rwsem_rlock_fast) /* # of fast read locks acquired */
65LOCK_EVENT(rwsem_rlock_fail) /* # of failed read lock acquisitions */67LOCK_EVENT(rwsem_rlock_fail) /* # of failed read lock acquisitions */
kernel/locking/rwsem.c+129−4
@@ -59,6 +59,42 @@ · 58 unmodified lines
59 * seems to hang on a reader owned rwsem especially if only one reader59 * seems to hang on a reader owned rwsem especially if only one reader
60 * is involved. Ideally we would like to track all the readers that own60 * is involved. Ideally we would like to track all the readers that own
61 * a rwsem, but the overhead is simply too big.61 * a rwsem, but the overhead is simply too big.
62 *
63 * Reader optimistic spinning is helpful when the reader critical section
64 * is short and there aren't that many readers around. It makes readers
65 * relatively more preferred than writers. When a writer times out spinning
66 * on a reader-owned lock and set the nospinnable bits, there are two main
67 * reasons for that.
68 *
69 * 1) The reader critical section is long, perhaps the task sleeps after
70 * acquiring the read lock.
71 * 2) There are just too many readers contending the lock causing it to
72 * take a while to service all of them.
73 *
74 * In the former case, long reader critical section will impede the progress
75 * of writers which is usually more important for system performance. In
76 * the later case, reader optimistic spinning tends to make the reader
77 * groups that contain readers that acquire the lock together smaller
78 * leading to more of them. That may hurt performance in some cases. In
79 * other words, the setting of nonspinnable bits indicates that reader
80 * optimistic spinning may not be helpful for those workloads that cause
81 * it.
82 *
83 * Therefore, any writers that had observed the setting of the writer
84 * nonspinnable bit for a given rwsem after they fail to acquire the lock
85 * via optimistic spinning will set the reader nonspinnable bit once they
86 * acquire the write lock. Similarly, readers that observe the setting
87 * of reader nonspinnable bit at slowpath entry will set the reader
88 * nonspinnable bits when they acquire the read lock via the wakeup path.
89 *
90 * Once the reader nonspinnable bit is on, it will only be reset when
91 * a writer is able to acquire the rwsem in the fast path or somehow a
92 * reader or writer in the slowpath doesn't observe the nonspinable bit.
93 *
94 * This is to discourage reader optmistic spinning on that particular
95 * rwsem and make writers more preferred. This adaptive disabling of reader
96 * optimistic spinning will alleviate the negative side effect of this
97 * feature.
62 */98 */
63#define RWSEM_READER_OWNED (1UL << 0)99#define RWSEM_READER_OWNED (1UL << 0)
64#define RWSEM_RD_NONSPINNABLE (1UL << 1)100#define RWSEM_RD_NONSPINNABLE (1UL << 1)
@@ -144,11 +180,14 @@ static inline bool rwsem_test_oflags(struct rw_semaphore *sem, long flags) · 79 unmodified lines
144 * Note that the owner value just indicates the task has owned the rwsem180 * Note that the owner value just indicates the task has owned the rwsem
145 * previously, it may not be the real owner or one of the real owners181 * previously, it may not be the real owner or one of the real owners
146 * anymore when that field is examined, so take it with a grain of salt.182 * anymore when that field is examined, so take it with a grain of salt.
183 *
184 * The reader non-spinnable bit is preserved.
147 */185 */
148static inline void __rwsem_set_reader_owned(struct rw_semaphore *sem,186static inline void __rwsem_set_reader_owned(struct rw_semaphore *sem,
149 struct task_struct *owner)187 struct task_struct *owner)
150{188{
151 unsigned long val = (unsigned long)owner | RWSEM_READER_OWNED;189 unsigned long val = (unsigned long)owner | RWSEM_READER_OWNED |
190 (atomic_long_read(&sem->owner) & RWSEM_RD_NONSPINNABLE);
152 191
153 atomic_long_set(&sem->owner, val);192 atomic_long_set(&sem->owner, val);
154}193}
@@ -287,6 +326,7 @@ struct rwsem_waiter { · 132 unmodified lines
287 struct task_struct *task;326 struct task_struct *task;
288 enum rwsem_waiter_type type;327 enum rwsem_waiter_type type;
289 unsigned long timeout;328 unsigned long timeout;
329 unsigned long last_rowner;
290};330};
291#define rwsem_first_waiter(sem) \331#define rwsem_first_waiter(sem) \
292 list_first_entry(&sem->wait_list, struct rwsem_waiter, list)332 list_first_entry(&sem->wait_list, struct rwsem_waiter, list)
@@ -368,6 +408,8 @@ static void rwsem_mark_wake(struct rw_semaphore *sem, · 75 unmodified lines
368 * so we can bail out early if a writer stole the lock.408 * so we can bail out early if a writer stole the lock.
369 */409 */
370 if (wake_type != RWSEM_WAKE_READ_OWNED) {410 if (wake_type != RWSEM_WAKE_READ_OWNED) {
411 struct task_struct *owner;
412
371 adjustment = RWSEM_READER_BIAS;413 adjustment = RWSEM_READER_BIAS;
372 oldcount = atomic_long_fetch_add(adjustment, &sem->count);414 oldcount = atomic_long_fetch_add(adjustment, &sem->count);
373 if (unlikely(oldcount & RWSEM_WRITER_MASK)) {415 if (unlikely(oldcount & RWSEM_WRITER_MASK)) {
@@ -388,8 +430,15 @@ static void rwsem_mark_wake(struct rw_semaphore *sem, · 14 unmodified lines
388 /*430 /*
389 * Set it to reader-owned to give spinners an early431 * Set it to reader-owned to give spinners an early
390 * indication that readers now have the lock.432 * indication that readers now have the lock.
433 * The reader nonspinnable bit seen at slowpath entry of
434 * the reader is copied over.
391 */435 */
392 __rwsem_set_reader_owned(sem, waiter->task);436 owner = waiter->task;
437 if (waiter->last_rowner & RWSEM_RD_NONSPINNABLE) {
438 owner = (void *)((unsigned long)owner | RWSEM_RD_NONSPINNABLE);
439 lockevent_inc(rwsem_opt_norspin);
440 }
441 __rwsem_set_reader_owned(sem, owner);
393 }442 }
394 443
395 /*444 /*
@@ -836,6 +885,42 @@ static inline void clear_wr_nonspinnable(struct rw_semaphore *sem) · 440 unmodified lines
836 if (rwsem_test_oflags(sem, RWSEM_WR_NONSPINNABLE))885 if (rwsem_test_oflags(sem, RWSEM_WR_NONSPINNABLE))
837 atomic_long_andnot(RWSEM_WR_NONSPINNABLE, &sem->owner);886 atomic_long_andnot(RWSEM_WR_NONSPINNABLE, &sem->owner);
838}887}
888
889/*
890 * This function is called when the reader fails to acquire the lock via
891 * optimistic spinning. In this case we will still attempt to do a trylock
892 * when comparing the rwsem state right now with the state when entering
893 * the slowpath indicates that the reader is still in a valid reader phase.
894 * This happens when the following conditions are true:
895 *
896 * 1) The lock is currently reader owned, and
897 * 2) The lock is previously not reader-owned or the last read owner changes.
898 *
899 * In the former case, we have transitioned from a writer phase to a
900 * reader-phase while spinning. In the latter case, it means the reader
901 * phase hasn't ended when we entered the optimistic spinning loop. In
902 * both cases, the reader is eligible to acquire the lock. This is the
903 * secondary path where a read lock is acquired optimistically.
904 *
905 * The reader non-spinnable bit wasn't set at time of entry or it will
906 * not be here at all.
907 */
908static inline bool rwsem_reader_phase_trylock(struct rw_semaphore *sem,
909 unsigned long last_rowner)
910{
911 unsigned long owner = atomic_long_read(&sem->owner);
912
913 if (!(owner & RWSEM_READER_OWNED))
914 return false;
915
916 if (((owner ^ last_rowner) & ~RWSEM_OWNER_FLAGS_MASK) &&
917 rwsem_try_read_lock_unqueued(sem)) {
918 lockevent_inc(rwsem_opt_rlock2);
919 lockevent_add(rwsem_opt_fail, -1);
920 return true;
921 }
922 return false;
923}
839#else924#else
840static inline bool rwsem_can_spin_on_owner(struct rw_semaphore *sem,925static inline bool rwsem_can_spin_on_owner(struct rw_semaphore *sem,
841 unsigned long nonspinnable)926 unsigned long nonspinnable)
@@ -849,6 +934,12 @@ static inline bool rwsem_optimistic_spin(struct rw_semaphore *sem, bool wlock) · 7 unmodified lines
849}934}
850 935
851static inline void clear_wr_nonspinnable(struct rw_semaphore *sem) { }936static inline void clear_wr_nonspinnable(struct rw_semaphore *sem) { }
937
938static inline bool rwsem_reader_phase_trylock(struct rw_semaphore *sem,
939 unsigned long last_rowner)
940{
941 return false;
942}
852#endif943#endif
853 944
854/*945/*
@@ -862,6 +953,14 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, int state) · 7 unmodified lines
862 struct rwsem_waiter waiter;953 struct rwsem_waiter waiter;
863 DEFINE_WAKE_Q(wake_q);954 DEFINE_WAKE_Q(wake_q);
864 955
956 /*
957 * Save the current read-owner of rwsem, if available, and the
958 * reader nonspinnable bit.
959 */
960 waiter.last_rowner = atomic_long_read(&sem->owner);
961 if (!(waiter.last_rowner & RWSEM_READER_OWNED))
962 waiter.last_rowner &= RWSEM_RD_NONSPINNABLE;
963
865 if (!rwsem_can_spin_on_owner(sem, RWSEM_RD_NONSPINNABLE))964 if (!rwsem_can_spin_on_owner(sem, RWSEM_RD_NONSPINNABLE))
866 goto queue;965 goto queue;
867 966
@@ -884,6 +983,8 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, int state) · 16 unmodified lines
884 wake_up_q(&wake_q);983 wake_up_q(&wake_q);
885 }984 }
886 return sem;985 return sem;
986 } else if (rwsem_reader_phase_trylock(sem, waiter.last_rowner)) {
987 return sem;
887 }988 }
888 989
889queue:990queue:
@@ -964,6 +1065,19 @@ out_nolock: · 74 unmodified lines
964 return ERR_PTR(-EINTR);1065 return ERR_PTR(-EINTR);
965}1066}
966 1067
1068/*
1069 * This function is called by the a write lock owner. So the owner value
1070 * won't get changed by others.
1071 */
1072static inline void rwsem_disable_reader_optspin(struct rw_semaphore *sem,
1073 bool disable)
1074{
1075 if (unlikely(disable)) {
1076 atomic_long_or(RWSEM_RD_NONSPINNABLE, &sem->owner);
1077 lockevent_inc(rwsem_opt_norspin);
1078 }
1079}
1080
967/*1081/*
968 * Wait until we successfully acquire the write lock1082 * Wait until we successfully acquire the write lock
969 */1083 */
@@ -971,6 +1085,7 @@ static struct rw_semaphore * · 1 unmodified line
971rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)1085rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
972{1086{
973 long count;1087 long count;
1088 bool disable_rspin;
974 enum writer_wait_state wstate;1089 enum writer_wait_state wstate;
975 struct rwsem_waiter waiter;1090 struct rwsem_waiter waiter;
976 struct rw_semaphore *ret = sem;1091 struct rw_semaphore *ret = sem;
@@ -981,6 +1096,13 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state) · 4 unmodified lines
981 rwsem_optimistic_spin(sem, true))1096 rwsem_optimistic_spin(sem, true))
982 return sem;1097 return sem;
983 1098
1099 /*
1100 * Disable reader optimistic spinning for this rwsem after
1101 * acquiring the write lock when the setting of the nonspinnable
1102 * bits are observed.
1103 */
1104 disable_rspin = atomic_long_read(&sem->owner) & RWSEM_NONSPINNABLE;
1105
984 /*1106 /*
985 * Optimistic spinning failed, proceed to the slowpath1107 * Optimistic spinning failed, proceed to the slowpath
986 * and block until we can acquire the sem.1108 * and block until we can acquire the sem.
@@ -1077,6 +1199,7 @@ wait: · 90 unmodified lines
1077 }1199 }
1078 __set_current_state(TASK_RUNNING);1200 __set_current_state(TASK_RUNNING);
1079 list_del(&waiter.list);1201 list_del(&waiter.list);
1202 rwsem_disable_reader_optspin(sem, disable_rspin);
1080 raw_spin_unlock_irq(&sem->wait_lock);1203 raw_spin_unlock_irq(&sem->wait_lock);
1081 lockevent_inc(rwsem_wlock);1204 lockevent_inc(rwsem_wlock);
1082 1205
@@ -1196,7 +1319,8 @@ static inline void __down_write(struct rw_semaphore *sem) · 113 unmodified lines
1196 if (unlikely(!atomic_long_try_cmpxchg_acquire(&sem->count, &tmp,1319 if (unlikely(!atomic_long_try_cmpxchg_acquire(&sem->count, &tmp,
1197 RWSEM_WRITER_LOCKED)))1320 RWSEM_WRITER_LOCKED)))
1198 rwsem_down_write_slowpath(sem, TASK_UNINTERRUPTIBLE);1321 rwsem_down_write_slowpath(sem, TASK_UNINTERRUPTIBLE);
1199 rwsem_set_owner(sem);1322 else
1323 rwsem_set_owner(sem);
1200}1324}
1201 1325
1202static inline int __down_write_killable(struct rw_semaphore *sem)1326static inline int __down_write_killable(struct rw_semaphore *sem)
@@ -1207,8 +1331,9 @@ static inline int __down_write_killable(struct rw_semaphore *sem) · 4 unmodified lines
1207 RWSEM_WRITER_LOCKED))) {1331 RWSEM_WRITER_LOCKED))) {
1208 if (IS_ERR(rwsem_down_write_slowpath(sem, TASK_KILLABLE)))1332 if (IS_ERR(rwsem_down_write_slowpath(sem, TASK_KILLABLE)))
1209 return -EINTR;1333 return -EINTR;
1334 } else {
1335 rwsem_set_owner(sem);
1210 }1336 }
1211 rwsem_set_owner(sem);
1212 return 0;1337 return 0;
1213}1338}
1214 1339