Simplify the adaptive spinning algorithm in rwlock and mutex:
currently, before to spin the turnstile spinlock is acquired and the waiters flag is set. This is not strictly necessary, so just spin before to acquire the spinlock and to set the flags. This will simplify a lot other functions too, as now we have the waiters flag set only if there are actually waiters. This should make wakeup/sleeping couplet faster under intensive mutex workload. This also fixes a bug in rw_try_upgrade() in the adaptive case, where turnstile_lookup() will recurse on the ts_lock lock that will never be really released [1]. [1] Reported by: jeff with Nokia help Tested by: pho, kris (earlier, bugged version of rwlock part) Discussed with: jhb [2], jeff MFC after: 1 week [2] John had a similar patch about 6.x and/or 7.x about mutexes probabilly
This commit is contained in:
parent
221a97c194
commit
49aead8a10
@ -335,6 +335,31 @@ _mtx_lock_sleep(struct mtx *m, uintptr_t tid, int opts, const char *file,
|
||||
m->lock_object.lo_name, (void *)m->mtx_lock, file, line);
|
||||
|
||||
while (!_obtain_lock(m, tid)) {
|
||||
#ifdef ADAPTIVE_MUTEXES
|
||||
/*
|
||||
* If the owner is running on another CPU, spin until the
|
||||
* owner stops running or the state of the lock changes.
|
||||
*/
|
||||
v = m->mtx_lock;
|
||||
if (v != MTX_UNOWNED) {
|
||||
owner = (struct thread *)(v & ~MTX_FLAGMASK);
|
||||
#ifdef ADAPTIVE_GIANT
|
||||
if (TD_IS_RUNNING(owner)) {
|
||||
#else
|
||||
if (m != &Giant && TD_IS_RUNNING(owner)) {
|
||||
#endif
|
||||
if (LOCK_LOG_TEST(&m->lock_object, 0))
|
||||
CTR3(KTR_LOCK,
|
||||
"%s: spinning on %p held by %p",
|
||||
__func__, m, owner);
|
||||
while (mtx_owner(m) == owner &&
|
||||
TD_IS_RUNNING(owner))
|
||||
cpu_spinwait();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ts = turnstile_trywait(&m->lock_object);
|
||||
v = m->mtx_lock;
|
||||
|
||||
@ -350,6 +375,23 @@ _mtx_lock_sleep(struct mtx *m, uintptr_t tid, int opts, const char *file,
|
||||
|
||||
MPASS(v != MTX_CONTESTED);
|
||||
|
||||
#ifdef ADAPTIVE_MUTEXES
|
||||
/*
|
||||
* If the current owner of the lock is executing on another
|
||||
* CPU quit the hard path and try to spin.
|
||||
*/
|
||||
owner = (struct thread *)(v & ~MTX_FLAGMASK);
|
||||
#ifdef ADAPTIVE_GIANT
|
||||
if (TD_IS_RUNNING(owner)) {
|
||||
#else
|
||||
if (m != &Giant && TD_IS_RUNNING(owner)) {
|
||||
#endif
|
||||
turnstile_cancel(ts);
|
||||
cpu_spinwait();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If the mutex isn't already contested and a failure occurs
|
||||
* setting the contested bit, the mutex was either released
|
||||
@ -362,26 +404,6 @@ _mtx_lock_sleep(struct mtx *m, uintptr_t tid, int opts, const char *file,
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef ADAPTIVE_MUTEXES
|
||||
/*
|
||||
* If the current owner of the lock is executing on another
|
||||
* CPU, spin instead of blocking.
|
||||
*/
|
||||
owner = (struct thread *)(v & ~MTX_FLAGMASK);
|
||||
#ifdef ADAPTIVE_GIANT
|
||||
if (TD_IS_RUNNING(owner))
|
||||
#else
|
||||
if (m != &Giant && TD_IS_RUNNING(owner))
|
||||
#endif
|
||||
{
|
||||
turnstile_cancel(ts);
|
||||
while (mtx_owner(m) == owner && TD_IS_RUNNING(owner)) {
|
||||
cpu_spinwait();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#endif /* ADAPTIVE_MUTEXES */
|
||||
|
||||
/*
|
||||
* We definitely must sleep for this lock.
|
||||
*/
|
||||
@ -589,17 +611,7 @@ _mtx_unlock_sleep(struct mtx *m, int opts, const char *file, int line)
|
||||
if (LOCK_LOG_TEST(&m->lock_object, opts))
|
||||
CTR1(KTR_LOCK, "_mtx_unlock_sleep: %p contested", m);
|
||||
|
||||
#ifdef ADAPTIVE_MUTEXES
|
||||
if (ts == NULL) {
|
||||
_release_lock_quick(m);
|
||||
if (LOCK_LOG_TEST(&m->lock_object, opts))
|
||||
CTR1(KTR_LOCK, "_mtx_unlock_sleep: %p no sleepers", m);
|
||||
turnstile_chain_unlock(&m->lock_object);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
MPASS(ts != NULL);
|
||||
#endif
|
||||
turnstile_broadcast(ts, TS_EXCLUSIVE_QUEUE);
|
||||
_release_lock_quick(m);
|
||||
/*
|
||||
|
@ -290,6 +290,28 @@ _rw_rlock(struct rwlock *rw, const char *file, int line)
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
/*
|
||||
* If the owner is running on another CPU, spin until
|
||||
* the owner stops running or the state of the lock
|
||||
* changes.
|
||||
*/
|
||||
owner = (struct thread *)RW_OWNER(x);
|
||||
if (TD_IS_RUNNING(owner)) {
|
||||
if (LOCK_LOG_TEST(&rw->lock_object, 0))
|
||||
CTR3(KTR_LOCK, "%s: spinning on %p held by %p",
|
||||
__func__, rw, owner);
|
||||
#ifdef LOCK_PROFILING_SHARED
|
||||
lock_profile_obtain_lock_failed(&rw->lock_object,
|
||||
&contested, &waittime);
|
||||
#endif
|
||||
while ((struct thread*)RW_OWNER(rw->rw_lock) == owner &&
|
||||
TD_IS_RUNNING(owner))
|
||||
cpu_spinwait();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Okay, now it's the hard case. Some other thread already
|
||||
* has a write lock, so acquire the turnstile lock so we can
|
||||
@ -309,6 +331,19 @@ _rw_rlock(struct rwlock *rw, const char *file, int line)
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
/*
|
||||
* If the current owner of the lock is executing on another
|
||||
* CPU quit the hard path and try to spin.
|
||||
*/
|
||||
owner = (struct thread *)RW_OWNER(x);
|
||||
if (TD_IS_RUNNING(owner)) {
|
||||
turnstile_cancel(ts);
|
||||
cpu_spinwait();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Ok, it's still a write lock. If the RW_LOCK_READ_WAITERS
|
||||
* flag is already set, then we can go ahead and block. If
|
||||
@ -327,29 +362,6 @@ _rw_rlock(struct rwlock *rw, const char *file, int line)
|
||||
__func__, rw);
|
||||
}
|
||||
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
/*
|
||||
* If the owner is running on another CPU, spin until
|
||||
* the owner stops running or the state of the lock
|
||||
* changes.
|
||||
*/
|
||||
owner = (struct thread *)RW_OWNER(x);
|
||||
if (TD_IS_RUNNING(owner)) {
|
||||
turnstile_cancel(ts);
|
||||
if (LOCK_LOG_TEST(&rw->lock_object, 0))
|
||||
CTR3(KTR_LOCK, "%s: spinning on %p held by %p",
|
||||
__func__, rw, owner);
|
||||
#ifdef LOCK_PROFILING_SHARED
|
||||
lock_profile_obtain_lock_failed(&rw->lock_object,
|
||||
&contested, &waittime);
|
||||
#endif
|
||||
while ((struct thread*)RW_OWNER(rw->rw_lock)== owner &&
|
||||
TD_IS_RUNNING(owner))
|
||||
cpu_spinwait();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We were unable to acquire the lock and the read waiters
|
||||
* flag is set, so we must block on the turnstile.
|
||||
@ -532,6 +544,27 @@ _rw_wlock_hard(struct rwlock *rw, uintptr_t tid, const char *file, int line)
|
||||
rw->lock_object.lo_name, (void *)rw->rw_lock, file, line);
|
||||
|
||||
while (!_rw_write_lock(rw, tid)) {
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
/*
|
||||
* If the lock is write locked and the owner is
|
||||
* running on another CPU, spin until the owner stops
|
||||
* running or the state of the lock changes.
|
||||
*/
|
||||
v = rw->rw_lock;
|
||||
owner = (struct thread *)RW_OWNER(v);
|
||||
if (!(v & RW_LOCK_READ) && TD_IS_RUNNING(owner)) {
|
||||
if (LOCK_LOG_TEST(&rw->lock_object, 0))
|
||||
CTR3(KTR_LOCK, "%s: spinning on %p held by %p",
|
||||
__func__, rw, owner);
|
||||
lock_profile_obtain_lock_failed(&rw->lock_object,
|
||||
&contested, &waittime);
|
||||
while ((struct thread*)RW_OWNER(rw->rw_lock) == owner &&
|
||||
TD_IS_RUNNING(owner))
|
||||
cpu_spinwait();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
ts = turnstile_trywait(&rw->lock_object);
|
||||
v = rw->rw_lock;
|
||||
|
||||
@ -545,6 +578,21 @@ _rw_wlock_hard(struct rwlock *rw, uintptr_t tid, const char *file, int line)
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
/*
|
||||
* If the current owner of the lock is executing on another
|
||||
* CPU quit the hard path and try to spin.
|
||||
*/
|
||||
if (!(v & RW_LOCK_READ)) {
|
||||
owner = (struct thread *)RW_OWNER(v);
|
||||
if (TD_IS_RUNNING(owner)) {
|
||||
turnstile_cancel(ts);
|
||||
cpu_spinwait();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If the lock was released by a writer with both readers
|
||||
* and writers waiting and a reader hasn't woken up and
|
||||
@ -586,27 +634,6 @@ _rw_wlock_hard(struct rwlock *rw, uintptr_t tid, const char *file, int line)
|
||||
__func__, rw);
|
||||
}
|
||||
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
/*
|
||||
* If the lock is write locked and the owner is
|
||||
* running on another CPU, spin until the owner stops
|
||||
* running or the state of the lock changes.
|
||||
*/
|
||||
owner = (struct thread *)RW_OWNER(v);
|
||||
if (!(v & RW_LOCK_READ) && TD_IS_RUNNING(owner)) {
|
||||
turnstile_cancel(ts);
|
||||
if (LOCK_LOG_TEST(&rw->lock_object, 0))
|
||||
CTR3(KTR_LOCK, "%s: spinning on %p held by %p",
|
||||
__func__, rw, owner);
|
||||
lock_profile_obtain_lock_failed(&rw->lock_object,
|
||||
&contested, &waittime);
|
||||
while ((struct thread*)RW_OWNER(rw->rw_lock)== owner &&
|
||||
TD_IS_RUNNING(owner))
|
||||
cpu_spinwait();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We were unable to acquire the lock and the write waiters
|
||||
* flag is set, so we must block on the turnstile.
|
||||
@ -654,22 +681,7 @@ _rw_wunlock_hard(struct rwlock *rw, uintptr_t tid, const char *file, int line)
|
||||
turnstile_chain_lock(&rw->lock_object);
|
||||
ts = turnstile_lookup(&rw->lock_object);
|
||||
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
/*
|
||||
* There might not be a turnstile for this lock if all of
|
||||
* the waiters are adaptively spinning. In that case, just
|
||||
* reset the lock to the unlocked state and return.
|
||||
*/
|
||||
if (ts == NULL) {
|
||||
atomic_store_rel_ptr(&rw->rw_lock, RW_UNLOCKED);
|
||||
if (LOCK_LOG_TEST(&rw->lock_object, 0))
|
||||
CTR2(KTR_LOCK, "%s: %p no sleepers", __func__, rw);
|
||||
turnstile_chain_unlock(&rw->lock_object);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
MPASS(ts != NULL);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Use the same algo as sx locks for now. Prefer waking up shared
|
||||
@ -686,42 +698,14 @@ _rw_wunlock_hard(struct rwlock *rw, uintptr_t tid, const char *file, int line)
|
||||
* above. There is probably a potential priority inversion in
|
||||
* there that could be worked around either by waking both queues
|
||||
* of waiters or doing some complicated lock handoff gymnastics.
|
||||
*
|
||||
* Note that in the ADAPTIVE_RWLOCKS case, if both flags are
|
||||
* set, there might not be any actual writers on the turnstile
|
||||
* as they might all be spinning. In that case, we don't want
|
||||
* to preserve the RW_LOCK_WRITE_WAITERS flag as the turnstile
|
||||
* is going to go away once we wakeup all the readers.
|
||||
*/
|
||||
v = RW_UNLOCKED;
|
||||
if (rw->rw_lock & RW_LOCK_READ_WAITERS) {
|
||||
queue = TS_SHARED_QUEUE;
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
if (rw->rw_lock & RW_LOCK_WRITE_WAITERS &&
|
||||
!turnstile_empty(ts, TS_EXCLUSIVE_QUEUE))
|
||||
v |= RW_LOCK_WRITE_WAITERS;
|
||||
#else
|
||||
v |= (rw->rw_lock & RW_LOCK_WRITE_WAITERS);
|
||||
#endif
|
||||
} else
|
||||
queue = TS_EXCLUSIVE_QUEUE;
|
||||
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
/*
|
||||
* We have to make sure that we actually have waiters to
|
||||
* wakeup. If they are all spinning, then we just need to
|
||||
* disown the turnstile and return.
|
||||
*/
|
||||
if (turnstile_empty(ts, queue)) {
|
||||
if (LOCK_LOG_TEST(&rw->lock_object, 0))
|
||||
CTR2(KTR_LOCK, "%s: %p no sleepers 2", __func__, rw);
|
||||
atomic_store_rel_ptr(&rw->rw_lock, v);
|
||||
turnstile_disown(ts);
|
||||
turnstile_chain_unlock(&rw->lock_object);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Wake up all waiters for the specific queue. */
|
||||
if (LOCK_LOG_TEST(&rw->lock_object, 0))
|
||||
CTR3(KTR_LOCK, "%s: %p waking up %s waiters", __func__, rw,
|
||||
@ -772,19 +756,12 @@ _rw_try_upgrade(struct rwlock *rw, const char *file, int line)
|
||||
* Try to switch from one reader to a writer again. This time
|
||||
* we honor the current state of the RW_LOCK_WRITE_WAITERS
|
||||
* flag. If we obtain the lock with the flag set, then claim
|
||||
* ownership of the turnstile. In the ADAPTIVE_RWLOCKS case
|
||||
* it is possible for there to not be an associated turnstile
|
||||
* even though there are waiters if all of the waiters are
|
||||
* spinning.
|
||||
* ownership of the turnstile.
|
||||
*/
|
||||
v = rw->rw_lock & RW_LOCK_WRITE_WAITERS;
|
||||
success = atomic_cmpset_ptr(&rw->rw_lock, RW_READERS_LOCK(1) | v,
|
||||
tid | v);
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
if (success && v && turnstile_lookup(&rw->lock_object) != NULL)
|
||||
#else
|
||||
if (success && v)
|
||||
#endif
|
||||
turnstile_claim(ts);
|
||||
else
|
||||
turnstile_cancel(ts);
|
||||
@ -837,26 +814,9 @@ _rw_downgrade(struct rwlock *rw, const char *file, int line)
|
||||
* Downgrade from a write lock while preserving
|
||||
* RW_LOCK_WRITE_WAITERS and give up ownership of the
|
||||
* turnstile. If there are any read waiters, wake them up.
|
||||
*
|
||||
* For ADAPTIVE_RWLOCKS, we have to allow for the fact that
|
||||
* all of the read waiters might be spinning. In that case,
|
||||
* act as if RW_LOCK_READ_WAITERS is not set. Also, only
|
||||
* preserve the RW_LOCK_WRITE_WAITERS flag if at least one
|
||||
* writer is blocked on the turnstile.
|
||||
*/
|
||||
ts = turnstile_lookup(&rw->lock_object);
|
||||
#ifdef ADAPTIVE_RWLOCKS
|
||||
if (ts == NULL)
|
||||
v &= ~(RW_LOCK_READ_WAITERS | RW_LOCK_WRITE_WAITERS);
|
||||
else if (v & RW_LOCK_READ_WAITERS &&
|
||||
turnstile_empty(ts, TS_SHARED_QUEUE))
|
||||
v &= ~RW_LOCK_READ_WAITERS;
|
||||
else if (v & RW_LOCK_WRITE_WAITERS &&
|
||||
turnstile_empty(ts, TS_EXCLUSIVE_QUEUE))
|
||||
v &= ~RW_LOCK_WRITE_WAITERS;
|
||||
#else
|
||||
MPASS(ts != NULL);
|
||||
#endif
|
||||
if (v & RW_LOCK_READ_WAITERS)
|
||||
turnstile_broadcast(ts, TS_SHARED_QUEUE);
|
||||
atomic_store_rel_ptr(&rw->rw_lock, RW_READERS_LOCK(1) |
|
||||
|
Loading…
Reference in New Issue
Block a user