proxygen
folly::MicroLockCore Class Reference

#include <MicroLock.h>

Inheritance diagram for folly::MicroLockCore:
folly::MicroLockBase< MaxSpins, MaxYields >

Public Member Functions

void unlock (unsigned slot)
 
void unlock ()
 
void init ()
 

Protected Member Functions

detail::Futexword () const
 
uint32_t baseShift (unsigned slot) const
 
uint32_t heldBit (unsigned slot) const
 
uint32_t waitBit (unsigned slot) const
 

Static Protected Member Functions

static void lockSlowPath (uint32_t oldWord, detail::Futex<> *wordPtr, uint32_t slotHeldBit, unsigned maxSpins, unsigned maxYields)
 

Protected Attributes

uint8_t lock_
 

Detailed Description

Tiny exclusive lock that packs four lock slots into a single byte. Each slot is an independent real, sleeping lock. The default lock and unlock functions operate on slot zero, which modifies only the low two bits of the host byte.

You should zero-initialize the bits of a MicroLock that you intend to use.

If you're not space-constrained, prefer std::mutex, which will likely be faster, since it has more than two bits of information to work with.

You are free to put a MicroLock in a union with some other object. If, for example, you want to use the bottom two bits of a pointer as a lock, you can put a MicroLock in a union with the pointer and limit yourself to MicroLock slot zero, which will use the two least-significant bits in the bottom byte.

(Note that such a union is safe only because MicroLock is based on a character type, and even under a strict interpretation of C++'s aliasing rules, character types may alias anything.)

MicroLock uses a dirty trick: it actually operates on the full 32-bit, four-byte-aligned bit of memory into which it is embedded. It never modifies bits outside the ones it's defined to modify, but it accesses all the bits in the 32-bit memory location for purposes of futex management.

The MaxSpins template parameter controls the number of times we spin trying to acquire the lock. MaxYields controls the number of times we call sched_yield; once we've tried to acquire the lock MaxSpins + MaxYields times, we sleep on the lock futex. By adjusting these parameters, you can make MicroLock behave as much or as little like a conventional spinlock as you'd like.

Performance

With the default template options, the timings for uncontended acquire-then-release come out as follows on Intel(R) Xeon(R) CPU E5-2660 0 @ 2.20GHz, in /opt, as of the master tree at Tue, 01 Mar 2016 19:48:15.


folly/test/SmallLocksBenchmark.cpp relative time/iter iters/s

MicroSpinLockUncontendedBenchmark 13.46ns 74.28M PicoSpinLockUncontendedBenchmark 14.99ns 66.71M MicroLockUncontendedBenchmark 27.06ns 36.96M StdMutexUncontendedBenchmark 25.18ns 39.72M

VirtualFunctionCall 1.72ns 579.78M

(The virtual dispatch benchmark is provided for scale.)

While the uncontended case for MicroLock is competitive with the glibc 2.2.0 implementation of std::mutex, std::mutex is likely to be faster in the contended case, because we need to wake up all waiters when we release.

Make sure to benchmark your particular workload.

Definition at line 99 of file MicroLock.h.

Member Function Documentation

unsigned folly::MicroLockCore::baseShift ( unsigned  slot) const
inlineprotected

Definition at line 135 of file MicroLock.h.

References folly::kIsLittleEndian, lock_, uint32_t, and word().

Referenced by heldBit(), and waitBit().

135  {
136  assert(slot < CHAR_BIT / 2);
137 
138  unsigned offset_bytes = (unsigned)((uintptr_t)&lock_ - (uintptr_t)word());
139 
140  return (
141  unsigned)(kIsLittleEndian ? offset_bytes * CHAR_BIT + slot * 2 : CHAR_BIT * (sizeof(uint32_t) - offset_bytes - 1) + slot * 2);
142 }
constexpr auto kIsLittleEndian
Definition: Portability.h:278
detail::Futex * word() const
Definition: MicroLock.h:129
uint32_t folly::MicroLockCore::heldBit ( unsigned  slot) const
inlineprotected

Definition at line 144 of file MicroLock.h.

References baseShift().

Referenced by folly::MicroLockBase< MaxSpins, MaxYields >::lock(), folly::MicroLockBase< MaxSpins, MaxYields >::try_lock(), and unlock().

144  {
145  return 1U << (baseShift(slot) + 0);
146 }
uint32_t baseShift(unsigned slot) const
Definition: MicroLock.h:135
void folly::MicroLockCore::init ( )
inline

Definition at line 124 of file MicroLock.h.

124  {
125  lock_ = 0;
126  }
void folly::MicroLockCore::lockSlowPath ( uint32_t  oldWord,
detail::Futex<> *  wordPtr,
uint32_t  slotHeldBit,
unsigned  maxSpins,
unsigned  maxYields 
)
staticprotected

Definition at line 24 of file MicroLock.cpp.

References folly::asm_volatile_pause(), folly::detail::futexWait(), retry, uint32_t, and folly::fibers::yield().

Referenced by folly::MicroLockBase< MaxSpins, MaxYields >::lock().

29  {
30  uint32_t newWord;
31  unsigned spins = 0;
32  uint32_t slotWaitBit = slotHeldBit << 1;
33  uint32_t needWaitBit = 0;
34 
35 retry:
36  if ((oldWord & slotHeldBit) != 0) {
37  ++spins;
38  if (spins > maxSpins + maxYields) {
39  // Somebody appears to have the lock. Block waiting for the
40  // holder to unlock the lock. We set heldbit(slot) so that the
41  // lock holder knows to FUTEX_WAKE us.
42  newWord = oldWord | slotWaitBit;
43  if (newWord != oldWord) {
44  if (!wordPtr->compare_exchange_weak(
45  oldWord,
46  newWord,
47  std::memory_order_relaxed,
48  std::memory_order_relaxed)) {
49  goto retry;
50  }
51  }
52  detail::futexWait(wordPtr, newWord, slotHeldBit);
53  needWaitBit = slotWaitBit;
54  } else if (spins > maxSpins) {
55  // sched_yield(), but more portable
57  } else {
59  }
60  oldWord = wordPtr->load(std::memory_order_relaxed);
61  goto retry;
62  }
63 
64  newWord = oldWord | slotHeldBit | needWaitBit;
65  if (!wordPtr->compare_exchange_weak(
66  oldWord,
67  newWord,
68  std::memory_order_acquire,
69  std::memory_order_relaxed)) {
70  goto retry;
71  }
72 }
static constexpr StringPiece retry
FutexResult futexWait(const Futex *futex, uint32_t expected, uint32_t waitMask)
Definition: Futex-inl.h:100
void asm_volatile_pause()
Definition: Asm.h:37
void folly::MicroLockCore::unlock ( unsigned  slot)
inline

Definition at line 152 of file MicroLock.h.

References folly::detail::futexWake(), heldBit(), uint32_t, waitBit(), and word().

152  {
153  detail::Futex<>* wordPtr = word();
154  uint32_t oldWord;
155  uint32_t newWord;
156 
157  oldWord = wordPtr->load(std::memory_order_relaxed);
158  do {
159  assert(oldWord & heldBit(slot));
160  newWord = oldWord & ~(heldBit(slot) | waitBit(slot));
161  } while (!wordPtr->compare_exchange_weak(
162  oldWord, newWord, std::memory_order_release, std::memory_order_relaxed));
163 
164  if (oldWord & waitBit(slot)) {
165  detail::futexWake(wordPtr, 1, heldBit(slot));
166  }
167 }
uint32_t heldBit(unsigned slot) const
Definition: MicroLock.h:144
detail::Futex * word() const
Definition: MicroLock.h:129
uint32_t waitBit(unsigned slot) const
Definition: MicroLock.h:148
int futexWake(const Futex *futex, int count, uint32_t wakeMask)
Definition: Futex-inl.h:107
void folly::MicroLockCore::unlock ( )
inline

Definition at line 120 of file MicroLock.h.

120  {
121  unlock(0);
122  }
uint32_t folly::MicroLockCore::waitBit ( unsigned  slot) const
inlineprotected

Definition at line 148 of file MicroLock.h.

References baseShift().

Referenced by folly::MicroLockBase< MaxSpins, MaxYields >::lock(), and unlock().

148  {
149  return 1U << (baseShift(slot) + 1);
150 }
uint32_t baseShift(unsigned slot) const
Definition: MicroLock.h:135
detail::Futex * folly::MicroLockCore::word ( ) const
inlineprotected

Definition at line 129 of file MicroLock.h.

References lock_, and uint32_t.

Referenced by baseShift(), folly::MicroLockBase< MaxSpins, MaxYields >::lock(), folly::MicroLockBase< MaxSpins, MaxYields >::try_lock(), and unlock().

129  {
130  uintptr_t lockptr = (uintptr_t)&lock_;
131  lockptr &= ~(sizeof(uint32_t) - 1);
132  return (detail::Futex<>*)lockptr;
133 }

Member Data Documentation

uint8_t folly::MicroLockCore::lock_
protected

Definition at line 105 of file MicroLock.h.

Referenced by baseShift(), and word().


The documentation for this class was generated from the following files: