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
5656 LOCK_EVENT(rwsem_sleep_writer) /* # of writer sleeps */
5757 LOCK_EVENT(rwsem_wake_reader) /* # of reader wakeups */
5858 LOCK_EVENT(rwsem_wake_writer) /* # of writer wakeups */
59−LOCK_EVENT(rwsem_opt_rlock) /* # of read locks opt-spin acquired */
60−LOCK_EVENT(rwsem_opt_wlock) /* # of write locks opt-spin acquired */
61−LOCK_EVENT(rwsem_opt_fail) /* # of failed opt-spinnings */
62−LOCK_EVENT(rwsem_opt_nospin) /* # of disabled reader opt-spinnings */
59+LOCK_EVENT(rwsem_opt_rlock) /* # of opt-acquired read locks */
60+LOCK_EVENT(rwsem_opt_wlock) /* # of opt-acquired write locks */
61+LOCK_EVENT(rwsem_opt_fail) /* # of failed optspins */
62+LOCK_EVENT(rwsem_opt_nospin) /* # of disabled optspins */
63+LOCK_EVENT(rwsem_opt_norspin) /* # of disabled reader-only optspins */
64+LOCK_EVENT(rwsem_opt_rlock2) /* # of opt-acquired 2ndary read locks */
6365 LOCK_EVENT(rwsem_rlock) /* # of read locks acquired */
6466 LOCK_EVENT(rwsem_rlock_fast) /* # of fast read locks acquired */
6567 LOCK_EVENT(rwsem_rlock_fail) /* # of failed read lock acquisitions */
kernel/locking/rwsem.c+129−4
@@ -59,6 +59,42 @@ · 58 unmodified lines
5959 * seems to hang on a reader owned rwsem especially if only one reader
6060 * is involved. Ideally we would like to track all the readers that own
6161 * 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.
6298 */
6399 #define RWSEM_READER_OWNED (1UL << 0)
64100 #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
144180 * Note that the owner value just indicates the task has owned the rwsem
145181 * previously, it may not be the real owner or one of the real owners
146182 * anymore when that field is examined, so take it with a grain of salt.
183+ *
184+ * The reader non-spinnable bit is preserved.
147185 */
148186 static inline void __rwsem_set_reader_owned(struct rw_semaphore *sem,
149187 struct task_struct *owner)
150188 {
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);
152191
153192 atomic_long_set(&sem->owner, val);
154193 }
@@ -287,6 +326,7 @@ struct rwsem_waiter { · 132 unmodified lines
287326 struct task_struct *task;
288327 enum rwsem_waiter_type type;
289328 unsigned long timeout;
329+ unsigned long last_rowner;
290330 };
291331 #define rwsem_first_waiter(sem) \
292332 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
368408 * so we can bail out early if a writer stole the lock.
369409 */
370410 if (wake_type != RWSEM_WAKE_READ_OWNED) {
411+ struct task_struct *owner;
412+
371413 adjustment = RWSEM_READER_BIAS;
372414 oldcount = atomic_long_fetch_add(adjustment, &sem->count);
373415 if (unlikely(oldcount & RWSEM_WRITER_MASK)) {
@@ -388,8 +430,15 @@ static void rwsem_mark_wake(struct rw_semaphore *sem, · 14 unmodified lines
388430 /*
389431 * Set it to reader-owned to give spinners an early
390432 * indication that readers now have the lock.
433+ * The reader nonspinnable bit seen at slowpath entry of
434+ * the reader is copied over.
391435 */
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);
393442 }
394443
395444 /*
@@ -836,6 +885,42 @@ static inline void clear_wr_nonspinnable(struct rw_semaphore *sem) · 440 unmodified lines
836885 if (rwsem_test_oflags(sem, RWSEM_WR_NONSPINNABLE))
837886 atomic_long_andnot(RWSEM_WR_NONSPINNABLE, &sem->owner);
838887 }
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+ */
908+static 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+}
839924 #else
840925 static inline bool rwsem_can_spin_on_owner(struct rw_semaphore *sem,
841926 unsigned long nonspinnable)
@@ -849,6 +934,12 @@ static inline bool rwsem_optimistic_spin(struct rw_semaphore *sem, bool wlock) · 7 unmodified lines
849934 }
850935
851936 static inline void clear_wr_nonspinnable(struct rw_semaphore *sem) { }
937+
938+static inline bool rwsem_reader_phase_trylock(struct rw_semaphore *sem,
939+ unsigned long last_rowner)
940+{
941+ return false;
942+}
852943 #endif
853944
854945 /*
@@ -862,6 +953,14 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, int state) · 7 unmodified lines
862953 struct rwsem_waiter waiter;
863954 DEFINE_WAKE_Q(wake_q);
864955
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+
865964 if (!rwsem_can_spin_on_owner(sem, RWSEM_RD_NONSPINNABLE))
866965 goto queue;
867966
@@ -884,6 +983,8 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, int state) · 16 unmodified lines
884983 wake_up_q(&wake_q);
885984 }
886985 return sem;
986+ } else if (rwsem_reader_phase_trylock(sem, waiter.last_rowner)) {
987+ return sem;
887988 }
888989
889990 queue:
@@ -964,6 +1065,19 @@ out_nolock: · 74 unmodified lines
9641065 return ERR_PTR(-EINTR);
9651066 }
9661067
1068+/*
1069+ * This function is called by the a write lock owner. So the owner value
1070+ * won't get changed by others.
1071+ */
1072+static 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+
9671081 /*
9681082 * Wait until we successfully acquire the write lock
9691083 */
@@ -971,6 +1085,7 @@ static struct rw_semaphore * · 1 unmodified line
9711085 rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
9721086 {
9731087 long count;
1088+ bool disable_rspin;
9741089 enum writer_wait_state wstate;
9751090 struct rwsem_waiter waiter;
9761091 struct rw_semaphore *ret = sem;
@@ -981,6 +1096,13 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state) · 4 unmodified lines
9811096 rwsem_optimistic_spin(sem, true))
9821097 return sem;
9831098
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+
9841106 /*
9851107 * Optimistic spinning failed, proceed to the slowpath
9861108 * and block until we can acquire the sem.
@@ -1077,6 +1199,7 @@ wait: · 90 unmodified lines
10771199 }
10781200 __set_current_state(TASK_RUNNING);
10791201 list_del(&waiter.list);
1202+ rwsem_disable_reader_optspin(sem, disable_rspin);
10801203 raw_spin_unlock_irq(&sem->wait_lock);
10811204 lockevent_inc(rwsem_wlock);
10821205
@@ -1196,7 +1319,8 @@ static inline void __down_write(struct rw_semaphore *sem) · 113 unmodified lines
11961319 if (unlikely(!atomic_long_try_cmpxchg_acquire(&sem->count, &tmp,
11971320 RWSEM_WRITER_LOCKED)))
11981321 rwsem_down_write_slowpath(sem, TASK_UNINTERRUPTIBLE);
1199− rwsem_set_owner(sem);
1322+ else
1323+ rwsem_set_owner(sem);
12001324 }
12011325
12021326 static 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
12071331 RWSEM_WRITER_LOCKED))) {
12081332 if (IS_ERR(rwsem_down_write_slowpath(sem, TASK_KILLABLE)))
12091333 return -EINTR;
1334+ } else {
1335+ rwsem_set_owner(sem);
12101336 }
1211− rwsem_set_owner(sem);
12121337 return 0;
12131338 }
12141339