spinlock.cc
Go to the documentation of this file.
00001 // Copyright 2017 The Abseil Authors.
00002 //
00003 // Licensed under the Apache License, Version 2.0 (the "License");
00004 // you may not use this file except in compliance with the License.
00005 // You may obtain a copy of the License at
00006 //
00007 //      https://www.apache.org/licenses/LICENSE-2.0
00008 //
00009 // Unless required by applicable law or agreed to in writing, software
00010 // distributed under the License is distributed on an "AS IS" BASIS,
00011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00012 // See the License for the specific language governing permissions and
00013 // limitations under the License.
00014 
00015 #include "absl/base/internal/spinlock.h"
00016 
00017 #include <algorithm>
00018 #include <atomic>
00019 #include <limits>
00020 
00021 #include "absl/base/attributes.h"
00022 #include "absl/base/internal/atomic_hook.h"
00023 #include "absl/base/internal/cycleclock.h"
00024 #include "absl/base/internal/spinlock_wait.h"
00025 #include "absl/base/internal/sysinfo.h" /* For NumCPUs() */
00026 #include "absl/base/call_once.h"
00027 
00028 // Description of lock-word:
00029 //  31..00: [............................3][2][1][0]
00030 //
00031 //     [0]: kSpinLockHeld
00032 //     [1]: kSpinLockCooperative
00033 //     [2]: kSpinLockDisabledScheduling
00034 // [31..3]: ONLY kSpinLockSleeper OR
00035 //          Wait time in cycles >> PROFILE_TIMESTAMP_SHIFT
00036 //
00037 // Detailed descriptions:
00038 //
00039 // Bit [0]: The lock is considered held iff kSpinLockHeld is set.
00040 //
00041 // Bit [1]: Eligible waiters (e.g. Fibers) may co-operatively reschedule when
00042 //          contended iff kSpinLockCooperative is set.
00043 //
00044 // Bit [2]: This bit is exclusive from bit [1].  It is used only by a
00045 //          non-cooperative lock.  When set, indicates that scheduling was
00046 //          successfully disabled when the lock was acquired.  May be unset,
00047 //          even if non-cooperative, if a ThreadIdentity did not yet exist at
00048 //          time of acquisition.
00049 //
00050 // Bit [3]: If this is the only upper bit ([31..3]) set then this lock was
00051 //          acquired without contention, however, at least one waiter exists.
00052 //
00053 //          Otherwise, bits [31..3] represent the time spent by the current lock
00054 //          holder to acquire the lock.  There may be outstanding waiter(s).
00055 
00056 namespace absl {
00057 namespace base_internal {
00058 
00059 ABSL_CONST_INIT static base_internal::AtomicHook<void (*)(const void *lock,
00060                                                           int64_t wait_cycles)>
00061     submit_profile_data;
00062 
00063 void RegisterSpinLockProfiler(void (*fn)(const void *contendedlock,
00064                                          int64_t wait_cycles)) {
00065   submit_profile_data.Store(fn);
00066 }
00067 
00068 // Uncommon constructors.
00069 SpinLock::SpinLock(base_internal::SchedulingMode mode)
00070     : lockword_(IsCooperative(mode) ? kSpinLockCooperative : 0) {
00071   ABSL_TSAN_MUTEX_CREATE(this, __tsan_mutex_not_static);
00072 }
00073 
00074 SpinLock::SpinLock(base_internal::LinkerInitialized,
00075                    base_internal::SchedulingMode mode) {
00076   ABSL_TSAN_MUTEX_CREATE(this, 0);
00077   if (IsCooperative(mode)) {
00078     InitLinkerInitializedAndCooperative();
00079   }
00080   // Otherwise, lockword_ is already initialized.
00081 }
00082 
00083 // Static (linker initialized) spinlocks always start life as functional
00084 // non-cooperative locks.  When their static constructor does run, it will call
00085 // this initializer to augment the lockword with the cooperative bit.  By
00086 // actually taking the lock when we do this we avoid the need for an atomic
00087 // operation in the regular unlock path.
00088 //
00089 // SlowLock() must be careful to re-test for this bit so that any outstanding
00090 // waiters may be upgraded to cooperative status.
00091 void SpinLock::InitLinkerInitializedAndCooperative() {
00092   Lock();
00093   lockword_.fetch_or(kSpinLockCooperative, std::memory_order_relaxed);
00094   Unlock();
00095 }
00096 
00097 // Monitor the lock to see if its value changes within some time period
00098 // (adaptive_spin_count loop iterations). The last value read from the lock
00099 // is returned from the method.
00100 uint32_t SpinLock::SpinLoop() {
00101   // We are already in the slow path of SpinLock, initialize the
00102   // adaptive_spin_count here.
00103   ABSL_CONST_INIT static absl::once_flag init_adaptive_spin_count;
00104   ABSL_CONST_INIT static int adaptive_spin_count = 0;
00105   base_internal::LowLevelCallOnce(&init_adaptive_spin_count, []() {
00106     adaptive_spin_count = base_internal::NumCPUs() > 1 ? 1000 : 1;
00107   });
00108 
00109   int c = adaptive_spin_count;
00110   uint32_t lock_value;
00111   do {
00112     lock_value = lockword_.load(std::memory_order_relaxed);
00113   } while ((lock_value & kSpinLockHeld) != 0 && --c > 0);
00114   return lock_value;
00115 }
00116 
00117 void SpinLock::SlowLock() {
00118   uint32_t lock_value = SpinLoop();
00119   lock_value = TryLockInternal(lock_value, 0);
00120   if ((lock_value & kSpinLockHeld) == 0) {
00121     return;
00122   }
00123   // The lock was not obtained initially, so this thread needs to wait for
00124   // it.  Record the current timestamp in the local variable wait_start_time
00125   // so the total wait time can be stored in the lockword once this thread
00126   // obtains the lock.
00127   int64_t wait_start_time = CycleClock::Now();
00128   uint32_t wait_cycles = 0;
00129   int lock_wait_call_count = 0;
00130   while ((lock_value & kSpinLockHeld) != 0) {
00131     // If the lock is currently held, but not marked as having a sleeper, mark
00132     // it as having a sleeper.
00133     if ((lock_value & kWaitTimeMask) == 0) {
00134       // Here, just "mark" that the thread is going to sleep.  Don't store the
00135       // lock wait time in the lock as that will cause the current lock
00136       // owner to think it experienced contention.
00137       if (lockword_.compare_exchange_strong(
00138               lock_value, lock_value | kSpinLockSleeper,
00139               std::memory_order_relaxed, std::memory_order_relaxed)) {
00140         // Successfully transitioned to kSpinLockSleeper.  Pass
00141         // kSpinLockSleeper to the SpinLockWait routine to properly indicate
00142         // the last lock_value observed.
00143         lock_value |= kSpinLockSleeper;
00144       } else if ((lock_value & kSpinLockHeld) == 0) {
00145         // Lock is free again, so try and acquire it before sleeping.  The
00146         // new lock state will be the number of cycles this thread waited if
00147         // this thread obtains the lock.
00148         lock_value = TryLockInternal(lock_value, wait_cycles);
00149         continue;   // Skip the delay at the end of the loop.
00150       }
00151     }
00152 
00153     base_internal::SchedulingMode scheduling_mode;
00154     if ((lock_value & kSpinLockCooperative) != 0) {
00155       scheduling_mode = base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL;
00156     } else {
00157       scheduling_mode = base_internal::SCHEDULE_KERNEL_ONLY;
00158     }
00159     // SpinLockDelay() calls into fiber scheduler, we need to see
00160     // synchronization there to avoid false positives.
00161     ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0);
00162     // Wait for an OS specific delay.
00163     base_internal::SpinLockDelay(&lockword_, lock_value, ++lock_wait_call_count,
00164                                  scheduling_mode);
00165     ABSL_TSAN_MUTEX_POST_DIVERT(this, 0);
00166     // Spin again after returning from the wait routine to give this thread
00167     // some chance of obtaining the lock.
00168     lock_value = SpinLoop();
00169     wait_cycles = EncodeWaitCycles(wait_start_time, CycleClock::Now());
00170     lock_value = TryLockInternal(lock_value, wait_cycles);
00171   }
00172 }
00173 
00174 void SpinLock::SlowUnlock(uint32_t lock_value) {
00175   base_internal::SpinLockWake(&lockword_,
00176                               false);  // wake waiter if necessary
00177 
00178   // If our acquisition was contended, collect contentionz profile info.  We
00179   // reserve a unitary wait time to represent that a waiter exists without our
00180   // own acquisition having been contended.
00181   if ((lock_value & kWaitTimeMask) != kSpinLockSleeper) {
00182     const uint64_t wait_cycles = DecodeWaitCycles(lock_value);
00183     ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0);
00184     submit_profile_data(this, wait_cycles);
00185     ABSL_TSAN_MUTEX_POST_DIVERT(this, 0);
00186   }
00187 }
00188 
00189 // We use the upper 29 bits of the lock word to store the time spent waiting to
00190 // acquire this lock.  This is reported by contentionz profiling.  Since the
00191 // lower bits of the cycle counter wrap very quickly on high-frequency
00192 // processors we divide to reduce the granularity to 2^PROFILE_TIMESTAMP_SHIFT
00193 // sized units.  On a 4Ghz machine this will lose track of wait times greater
00194 // than (2^29/4 Ghz)*128 =~ 17.2 seconds.  Such waits should be extremely rare.
00195 enum { PROFILE_TIMESTAMP_SHIFT = 7 };
00196 enum { LOCKWORD_RESERVED_SHIFT = 3 };  // We currently reserve the lower 3 bits.
00197 
00198 uint32_t SpinLock::EncodeWaitCycles(int64_t wait_start_time,
00199                                     int64_t wait_end_time) {
00200   static const int64_t kMaxWaitTime =
00201       std::numeric_limits<uint32_t>::max() >> LOCKWORD_RESERVED_SHIFT;
00202   int64_t scaled_wait_time =
00203       (wait_end_time - wait_start_time) >> PROFILE_TIMESTAMP_SHIFT;
00204 
00205   // Return a representation of the time spent waiting that can be stored in
00206   // the lock word's upper bits.
00207   uint32_t clamped = static_cast<uint32_t>(
00208       std::min(scaled_wait_time, kMaxWaitTime) << LOCKWORD_RESERVED_SHIFT);
00209 
00210   if (clamped == 0) {
00211     return kSpinLockSleeper;  // Just wake waiters, but don't record contention.
00212   }
00213   // Bump up value if necessary to avoid returning kSpinLockSleeper.
00214   const uint32_t kMinWaitTime =
00215       kSpinLockSleeper + (1 << LOCKWORD_RESERVED_SHIFT);
00216   if (clamped == kSpinLockSleeper) {
00217     return kMinWaitTime;
00218   }
00219   return clamped;
00220 }
00221 
00222 uint64_t SpinLock::DecodeWaitCycles(uint32_t lock_value) {
00223   // Cast to uint32_t first to ensure bits [63:32] are cleared.
00224   const uint64_t scaled_wait_time =
00225       static_cast<uint32_t>(lock_value & kWaitTimeMask);
00226   return scaled_wait_time
00227       << (PROFILE_TIMESTAMP_SHIFT - LOCKWORD_RESERVED_SHIFT);
00228 }
00229 
00230 }  // namespace base_internal
00231 }  // namespace absl


abseil_cpp
Author(s):
autogenerated on Wed Jun 19 2019 19:42:15