libthread_xu - Improve contended mutex code
authorMatthew Dillon <dillon@apollo.backplane.com>
Mon, 23 Apr 2018 02:29:58 +0000 (19:29 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Mon, 23 Apr 2018 02:31:28 +0000 (19:31 -0700)
* Retry a few times in a loop before using the umtx*() system
  calls to sleep.

  This significantly reduces IPI traffic and improves performance
  when a mutex is heavily contended.

lib/libthread_xu/thread/thr_umtx.c
lib/libthread_xu/thread/thr_umtx.h

index 9376add..0ef4935 100644 (file)
 int
 __thr_umtx_lock(volatile umtx_t *mtx, int id, int timo)
 {
-       int v, errval, ret = 0;
+       int v;
+       int errval;
+       int ret = 0;
+       int retry = 4;
 
        v = *mtx;
        cpu_ccfence();
        id &= 0x3FFFFFFF;
 
        for (;;) {
+               cpu_pause();
                if (v == 0) {
-                       if (atomic_cmpset_acq_int(mtx, 0, id)) {
+                       if (atomic_fcmpset_int(mtx, &v, id))
                                break;
-                       }
                        continue;
                }
-               if (atomic_fcmpset_int(mtx, &v, v|0x40000000)) {
+               if (--retry) {
+                       sched_yield();
+                       v = *mtx;
+                       continue;
+               }
+
+               /*
+                * Set the waiting bit.  If the fcmpset fails v is loaded
+                * with the current content of the mutex, and if the waiting
+                * bit is already set, we can also sleep.
+                */
+               if (atomic_fcmpset_int(mtx, &v, v|0x40000000) ||
+                   (v & 0x40000000)) {
                        if (timo == 0) {
                                _umtx_sleep_err(mtx, v|0x40000000, timo);
                        } else if ((errval = _umtx_sleep_err(mtx, v|0x40000000, timo)) > 0) {
@@ -71,32 +86,23 @@ __thr_umtx_lock(volatile umtx_t *mtx, int id, int timo)
                                }
                        }
                }
+               retry = 4;
        }
        return (ret);
 }
 
 /*
- * Release a mutex.  A contested mutex has a value
- * of 2, an uncontested mutex has a value of 1.
+ * Inline followup when releasing a mutex.  The mutex has been released
+ * but 'v' either doesn't match id or needs a wakeup.
  */
 void
-__thr_umtx_unlock(volatile umtx_t *mtx, int id)
+__thr_umtx_unlock(volatile umtx_t *mtx, int v, int id)
 {
-       int v;
-
-       v = *mtx;
-       cpu_ccfence();
-       id &= 0x3FFFFFFF;
-
-       for (;;) {
-               if (atomic_fcmpset_int(mtx, &v, 0)) {
-                       if (v & 0x40000000)
-                               _umtx_wakeup_err(mtx, 0);
-                       THR_ASSERT((v & 0x3FFFFFFF) == id,
-                                  "thr_umtx_unlock: wrong owner");
-                       break;
-               }
+       if (v & 0x40000000) {
+               _umtx_wakeup_err(mtx, 0);
+               v &= 0x3FFFFFFF;
        }
+       THR_ASSERT(v == id, "thr_umtx_unlock: wrong owner");
 }
 
 /*
index 36ba769..f048d1c 100644 (file)
 #define UMTX_LOCKED    1
 #define UMTX_CONTESTED 2
 
+#define        cpu_pause()     __asm __volatile("pause":::"memory")
+
 typedef int umtx_t;
 
 int    __thr_umtx_lock(volatile umtx_t *mtx, int id, int timo);
 int    __thr_umtx_timedlock(volatile umtx_t *mtx, int id,
                 const struct timespec *timeout);
-void   __thr_umtx_unlock(volatile umtx_t *mtx, int id);
+void   __thr_umtx_unlock(volatile umtx_t *mtx, int v, int id);
 
 static inline void
 _thr_umtx_init(volatile umtx_t *mtx)
@@ -49,18 +51,22 @@ _thr_umtx_init(volatile umtx_t *mtx)
 static inline int
 _thr_umtx_trylock(volatile umtx_t *mtx, int id)
 {
-       if (atomic_cmpset_acq_int(mtx, 0, id)) {
+       if (atomic_cmpset_acq_int(mtx, 0, id))
+               return (0);
+       cpu_pause();
+       if (atomic_cmpset_acq_int(mtx, 0, id))
+               return (0);
+       cpu_pause();
+       if (atomic_cmpset_acq_int(mtx, 0, id))
                return (0);
-       }
        return (EBUSY);
 }
 
 static inline int
 _thr_umtx_lock(volatile umtx_t *mtx, int id)
 {
-       if (atomic_cmpset_acq_int(mtx, 0, id)) {
+       if (atomic_cmpset_acq_int(mtx, 0, id))
                return (0);
-       }
        return (__thr_umtx_lock(mtx, id, 0));
 }
 
@@ -77,10 +83,11 @@ _thr_umtx_timedlock(volatile umtx_t *mtx, int id,
 static inline void
 _thr_umtx_unlock(volatile umtx_t *mtx, int id)
 {
-       if (atomic_cmpset_acq_int(mtx, id, 0)) {
-               return;
-       }
-       __thr_umtx_unlock(mtx, id);
+       int v;
+
+       v = atomic_swap_int(mtx, 0);
+       if (v != id)
+               __thr_umtx_unlock(mtx, v, id);
 }
 
 int _thr_umtx_wait(volatile umtx_t *mtx, umtx_t exp,