Bug 1457882 - Emulate glibc adaptive mutexes on OSX r=nfroyd
authorJon Coppeard <jcoppeard@mozilla.com>
Wed, 02 May 2018 15:11:53 +0100
changeset 416713 ef231f04c4ec6710dfb179a93cd526e019fc09a6
parent 416712 2a7aef0304b22b0dd3efbfb105ede8ea515d4fb0
child 416714 3ab2fbb175bd7fa720d4e990dbaaa0a5fe0c32a9
push id33933
push userrgurzau@mozilla.com
push dateWed, 02 May 2018 21:05:42 +0000
treeherdermozilla-central@2d83e1843241 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnfroyd
bugs1457882
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1457882 - Emulate glibc adaptive mutexes on OSX r=nfroyd
mozglue/misc/Mutex_posix.cpp
mozglue/misc/PlatformMutex.h
--- a/mozglue/misc/Mutex_posix.cpp
+++ b/mozglue/misc/Mutex_posix.cpp
@@ -1,34 +1,73 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Assertions.h"
 
+#include <algorithm>
 #include <errno.h>
 #include <pthread.h>
 #include <stdio.h>
+#include <unistd.h>
 
 #include "mozilla/PlatformMutex.h"
 #include "MutexPlatformData_posix.h"
 
+#define REPORT_PTHREADS_ERROR(result, msg)      \
+  {                                             \
+    errno = result;                             \
+    perror(msg);                                \
+    MOZ_CRASH(msg);                             \
+  }
+
 #define TRY_CALL_PTHREADS(call, msg)            \
   {                                             \
     int result = (call);                        \
     if (result != 0) {                          \
-      errno = result;                           \
-      perror(msg);                              \
-      MOZ_CRASH(msg);                           \
+      REPORT_PTHREADS_ERROR(result, msg);       \
     }                                           \
   }
 
+#ifdef XP_DARWIN
+
+// CPU count. Read concurrently from multiple threads. Written once during the
+// first mutex initialization; re-initialization is safe hence relaxed ordering
+// is OK.
+static mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed> sCPUCount(0);
+
+static void
+EnsureCPUCount()
+{
+  if (sCPUCount) {
+    return;
+  }
+
+  // _SC_NPROCESSORS_CONF and _SC_NPROCESSORS_ONLN are common, but not
+  // standard.
+#if defined(_SC_NPROCESSORS_CONF)
+  long n = sysconf(_SC_NPROCESSORS_CONF);
+  sCPUCount = (n > 0) ? uint32_t(n) : 1;
+#elif defined(_SC_NPROCESSORS_ONLN)
+  long n = sysconf(_SC_NPROCESSORS_ONLN);
+  sCPUCount = (n > 0) ? uint32_t(n) : 1;
+#else
+  sCPUCount = 1;
+#endif
+}
+
+#endif // XP_DARWIN
+
 mozilla::detail::MutexImpl::MutexImpl()
+#ifdef XP_DARWIN
+  : averageSpins(0)
+#endif
 {
   pthread_mutexattr_t* attrp = nullptr;
 
   // Linux with glibc and FreeBSD support adaptive mutexes that spin
   // for a short number of tries before sleeping.  NSPR's locks did
   // this, too, and it seems like a reasonable thing to do.
 #if (defined(__linux__) && defined(__GLIBC__)) || defined(__FreeBSD__)
 #define ADAPTIVE_MUTEX_SUPPORTED
@@ -55,29 +94,90 @@ mozilla::detail::MutexImpl::MutexImpl()
 
   TRY_CALL_PTHREADS(pthread_mutex_init(&platformData()->ptMutex, attrp),
                     "mozilla::detail::MutexImpl::MutexImpl: pthread_mutex_init failed");
 
 #if defined(ATTR_REQUIRED)
   TRY_CALL_PTHREADS(pthread_mutexattr_destroy(&attr),
                     "mozilla::detail::MutexImpl::MutexImpl: pthread_mutexattr_destroy failed");
 #endif
+
+#ifdef XP_DARWIN
+  EnsureCPUCount();
+#endif
 }
 
 mozilla::detail::MutexImpl::~MutexImpl()
 {
   TRY_CALL_PTHREADS(pthread_mutex_destroy(&platformData()->ptMutex),
                     "mozilla::detail::MutexImpl::~MutexImpl: pthread_mutex_destroy failed");
 }
 
+inline void
+mozilla::detail::MutexImpl::mutexLock()
+{
+  TRY_CALL_PTHREADS(pthread_mutex_lock(&platformData()->ptMutex),
+                    "mozilla::detail::MutexImpl::mutexLock: pthread_mutex_lock failed");
+}
+
+#ifdef XP_DARWIN
+inline bool
+mozilla::detail::MutexImpl::mutexTryLock()
+{
+  int result = pthread_mutex_trylock(&platformData()->ptMutex);
+  if (result == 0) {
+    return true;
+  }
+
+  if (result == EBUSY) {
+    return false;
+  }
+
+  REPORT_PTHREADS_ERROR(result,
+                        "mozilla::detail::MutexImpl::mutexTryLock: pthread_mutex_trylock failed");
+}
+#endif
+
 void
 mozilla::detail::MutexImpl::lock()
 {
-  TRY_CALL_PTHREADS(pthread_mutex_lock(&platformData()->ptMutex),
-                    "mozilla::detail::MutexImpl::lock: pthread_mutex_lock failed");
+#ifndef XP_DARWIN
+  mutexLock();
+#else
+  // Mutex performance on OSX can be very poor if there's a lot of contention as
+  // this causes excessive context switching. On Linux/FreeBSD we use the
+  // adaptive mutex type (PTHREAD_MUTEX_ADAPTIVE_NP) to address this, but this
+  // isn't available on OSX. The code below is a reimplementation of this
+  // feature.
+
+  MOZ_ASSERT(sCPUCount);
+  if (sCPUCount == 1) {
+    mutexLock();
+    return;
+  }
+
+  if (!mutexTryLock()) {
+    const int32_t SpinLimit = 100;
+
+    int32_t count = 0;
+    int32_t maxSpins = std::min(SpinLimit, 2 * averageSpins + 10);
+    do {
+      if (count >= maxSpins) {
+        mutexLock();
+        break;
+      }
+      asm("pause"); // Hint to the processor that we're spinning.
+      count++;
+    } while (!mutexTryLock());
+
+    // Update moving average.
+    averageSpins += (count - averageSpins) / 8;
+    MOZ_ASSERT(averageSpins >= 0 && averageSpins <= SpinLimit);
+  }
+#endif // XP_DARWIN
 }
 
 void
 mozilla::detail::MutexImpl::unlock()
 {
   TRY_CALL_PTHREADS(pthread_mutex_unlock(&platformData()->ptMutex),
                     "mozilla::detail::MutexImpl::unlock: pthread_mutex_unlock failed");
 }
--- a/mozglue/misc/PlatformMutex.h
+++ b/mozglue/misc/PlatformMutex.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_PlatformMutex_h
 #define mozilla_PlatformMutex_h
 
+#include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Move.h"
 
 #if !defined(XP_WIN)
 # include <pthread.h>
 #endif
 
 namespace mozilla {
@@ -37,23 +38,34 @@ protected:
   MFBT_API void unlock();
 
 private:
   MutexImpl(const MutexImpl&) = delete;
   void operator=(const MutexImpl&) = delete;
   MutexImpl(MutexImpl&&) = delete;
   void operator=(MutexImpl&&) = delete;
 
+  void mutexLock();
+#ifdef XP_DARWIN
+  bool mutexTryLock();
+#endif
+
   PlatformData* platformData();
 
 #if !defined(XP_WIN)
   void* platformData_[sizeof(pthread_mutex_t) / sizeof(void*)];
   static_assert(sizeof(pthread_mutex_t) / sizeof(void*) != 0 &&
                 sizeof(pthread_mutex_t) % sizeof(void*) == 0,
                 "pthread_mutex_t must have pointer alignment");
+#ifdef XP_DARWIN
+  // Moving average of the number of spins it takes to acquire the mutex if we
+  // have to wait. May be accessed by multiple threads concurrently. Getting the
+  // latest value is not essential hence relaxed memory ordering is sufficient.
+  mozilla::Atomic<int32_t, mozilla::MemoryOrdering::Relaxed> averageSpins;
+#endif
 #else
   void* platformData_[6];
 #endif
 
   friend class mozilla::detail::ConditionVariableImpl;
 };
 
 } // namespace detail