ca9856cabc
-- 53550735f5a943dfb99225e7c53f211c2d6e7951 by Gennadiy Rozental <rogeeff@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 309333648 -- 847bbf8a1d9cd322ec058c6f932d1f687fd3d331 by Gennadiy Rozental <rogeeff@google.com>: Make Validation interfaces private in CommandLineFlag. Calls are rewired via private interface access struct. PiperOrigin-RevId: 309323013 -- a600fc5051e0a0af50a7850450fd3ed1aef3f316 by Matthew Brown <matthewbr@google.com>: Internal Change. PiperOrigin-RevId: 309292207 -- 937d00ce3cf62c5f23f59b5377471fd01d6bfbc7 by Gennadiy Rozental <rogeeff@google.com>: Make TypeId interface private in CommandLineFlag. We also rewire the SaveState via the new PrivateHandleInterface trampoline class. This class will be the only way to access private methods of class CommandLineFlag. PiperOrigin-RevId: 309282547 -- 796c4bd35073b6a8337762bdb13603dae12a4df1 by Derek Mauro <dmauro@google.com>: Cleanup uses of kLinkerInitialized PiperOrigin-RevId: 309274734 -- c831446c52d9ef4bdcb1ea369840904620abc4b9 by Gennadiy Rozental <rogeeff@google.com>: Eliminate the interface IsModified of CommndLineFlag. PiperOrigin-RevId: 309256248 -- a1db59d7f7aa39cb0a37dbf80f8c04e371da8465 by Gennadiy Rozental <rogeeff@google.com>: Avoid default value generator if default value expression is constexpr. If possible, we detect constexpr-ness of default value expression and avoid storing default value generator in side of flag and instead set the flag's value to the value of that expression at const initialization time of flag objects. At the moment we only do this for flags of (all) integral, float and double value types PiperOrigin-RevId: 309110630 -- ae3b4a139aacd8fc165c9acd2a3cbae1f9e26af4 by Gennadiy Rozental <rogeeff@google.com>: Make SaveState a private method of the CommandLineFlag and make it only accessible from FlagSaverImpl. There is no other call sites for this call. PiperOrigin-RevId: 309073989 -- cbc24b4dcc166dd6b0208e9d7620484eaaaa7ee0 by Abseil Team <absl-team@google.com>: Eliminate the interface IsModified of CommndLineFlag. PiperOrigin-RevId: 309064639 -- 08e79645a89d71785c5381cea9c413357db9824a by Gennadiy Rozental <rogeeff@google.com>: Eliminate the interface IsModified of CommndLineFlag. PiperOrigin-RevId: 309054430 -- 4a6c70233c60dc8c39b7fa9beb5fa687c215261f by Gennadiy Rozental <rogeeff@google.com>: Internal change PiperOrigin-RevId: 308900784 -- 13160efdf7710f142778d5a1e4c85aa309f019b6 by Abseil Team <absl-team@google.com>: Provide definitions of static member variables -- improved C++11 support. PiperOrigin-RevId: 308900290 -- 0343b8228657b9b313afdfe88c4a7b2137d56db4 by Gennadiy Rozental <rogeeff@google.com>: Rename method Get<T> to TryGet<T> per approved spec before making interface public. PiperOrigin-RevId: 308889113 -- 7b84e27fb857fc1296a05504970f506d47d2f2c1 by Derek Mauro <dmauro@google.com>: Remove node_hash_* methods that were deprecated on release PiperOrigin-RevId: 308837933 -- 599d44ee72c02b6bb6e1c1a1db72873841441416 by Gennadiy Rozental <rogeeff@google.com>: Eliminate CommandLineFlag::Typename interface per approved spec before making CommandLineFlag public. PiperOrigin-RevId: 308814376 GitOrigin-RevId: 53550735f5a943dfb99225e7c53f211c2d6e7951 Change-Id: Iae52c65b7322152c7e58f222d60eb5a21699a2cb
241 lines
9.6 KiB
C++
241 lines
9.6 KiB
C++
// Copyright 2017 The Abseil Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "absl/base/internal/spinlock.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <limits>
|
|
|
|
#include "absl/base/attributes.h"
|
|
#include "absl/base/internal/atomic_hook.h"
|
|
#include "absl/base/internal/cycleclock.h"
|
|
#include "absl/base/internal/spinlock_wait.h"
|
|
#include "absl/base/internal/sysinfo.h" /* For NumCPUs() */
|
|
#include "absl/base/call_once.h"
|
|
|
|
// Description of lock-word:
|
|
// 31..00: [............................3][2][1][0]
|
|
//
|
|
// [0]: kSpinLockHeld
|
|
// [1]: kSpinLockCooperative
|
|
// [2]: kSpinLockDisabledScheduling
|
|
// [31..3]: ONLY kSpinLockSleeper OR
|
|
// Wait time in cycles >> PROFILE_TIMESTAMP_SHIFT
|
|
//
|
|
// Detailed descriptions:
|
|
//
|
|
// Bit [0]: The lock is considered held iff kSpinLockHeld is set.
|
|
//
|
|
// Bit [1]: Eligible waiters (e.g. Fibers) may co-operatively reschedule when
|
|
// contended iff kSpinLockCooperative is set.
|
|
//
|
|
// Bit [2]: This bit is exclusive from bit [1]. It is used only by a
|
|
// non-cooperative lock. When set, indicates that scheduling was
|
|
// successfully disabled when the lock was acquired. May be unset,
|
|
// even if non-cooperative, if a ThreadIdentity did not yet exist at
|
|
// time of acquisition.
|
|
//
|
|
// Bit [3]: If this is the only upper bit ([31..3]) set then this lock was
|
|
// acquired without contention, however, at least one waiter exists.
|
|
//
|
|
// Otherwise, bits [31..3] represent the time spent by the current lock
|
|
// holder to acquire the lock. There may be outstanding waiter(s).
|
|
|
|
namespace absl {
|
|
ABSL_NAMESPACE_BEGIN
|
|
namespace base_internal {
|
|
|
|
ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES static base_internal::AtomicHook<void (*)(
|
|
const void *lock, int64_t wait_cycles)>
|
|
submit_profile_data;
|
|
|
|
void RegisterSpinLockProfiler(void (*fn)(const void *contendedlock,
|
|
int64_t wait_cycles)) {
|
|
submit_profile_data.Store(fn);
|
|
}
|
|
|
|
// Static member variable definitions.
|
|
constexpr uint32_t SpinLock::kSpinLockHeld;
|
|
constexpr uint32_t SpinLock::kSpinLockCooperative;
|
|
constexpr uint32_t SpinLock::kSpinLockDisabledScheduling;
|
|
constexpr uint32_t SpinLock::kSpinLockSleeper;
|
|
constexpr uint32_t SpinLock::kWaitTimeMask;
|
|
|
|
// Uncommon constructors.
|
|
SpinLock::SpinLock(base_internal::SchedulingMode mode)
|
|
: lockword_(IsCooperative(mode) ? kSpinLockCooperative : 0) {
|
|
ABSL_TSAN_MUTEX_CREATE(this, __tsan_mutex_not_static);
|
|
}
|
|
|
|
SpinLock::SpinLock(base_internal::LinkerInitialized,
|
|
base_internal::SchedulingMode mode) {
|
|
ABSL_TSAN_MUTEX_CREATE(this, 0);
|
|
if (IsCooperative(mode)) {
|
|
InitLinkerInitializedAndCooperative();
|
|
}
|
|
// Otherwise, lockword_ is already initialized.
|
|
}
|
|
|
|
// Static (linker initialized) spinlocks always start life as functional
|
|
// non-cooperative locks. When their static constructor does run, it will call
|
|
// this initializer to augment the lockword with the cooperative bit. By
|
|
// actually taking the lock when we do this we avoid the need for an atomic
|
|
// operation in the regular unlock path.
|
|
//
|
|
// SlowLock() must be careful to re-test for this bit so that any outstanding
|
|
// waiters may be upgraded to cooperative status.
|
|
void SpinLock::InitLinkerInitializedAndCooperative() {
|
|
Lock();
|
|
lockword_.fetch_or(kSpinLockCooperative, std::memory_order_relaxed);
|
|
Unlock();
|
|
}
|
|
|
|
// Monitor the lock to see if its value changes within some time period
|
|
// (adaptive_spin_count loop iterations). The last value read from the lock
|
|
// is returned from the method.
|
|
uint32_t SpinLock::SpinLoop() {
|
|
// We are already in the slow path of SpinLock, initialize the
|
|
// adaptive_spin_count here.
|
|
ABSL_CONST_INIT static absl::once_flag init_adaptive_spin_count;
|
|
ABSL_CONST_INIT static int adaptive_spin_count = 0;
|
|
base_internal::LowLevelCallOnce(&init_adaptive_spin_count, []() {
|
|
adaptive_spin_count = base_internal::NumCPUs() > 1 ? 1000 : 1;
|
|
});
|
|
|
|
int c = adaptive_spin_count;
|
|
uint32_t lock_value;
|
|
do {
|
|
lock_value = lockword_.load(std::memory_order_relaxed);
|
|
} while ((lock_value & kSpinLockHeld) != 0 && --c > 0);
|
|
return lock_value;
|
|
}
|
|
|
|
void SpinLock::SlowLock() {
|
|
uint32_t lock_value = SpinLoop();
|
|
lock_value = TryLockInternal(lock_value, 0);
|
|
if ((lock_value & kSpinLockHeld) == 0) {
|
|
return;
|
|
}
|
|
// The lock was not obtained initially, so this thread needs to wait for
|
|
// it. Record the current timestamp in the local variable wait_start_time
|
|
// so the total wait time can be stored in the lockword once this thread
|
|
// obtains the lock.
|
|
int64_t wait_start_time = CycleClock::Now();
|
|
uint32_t wait_cycles = 0;
|
|
int lock_wait_call_count = 0;
|
|
while ((lock_value & kSpinLockHeld) != 0) {
|
|
// If the lock is currently held, but not marked as having a sleeper, mark
|
|
// it as having a sleeper.
|
|
if ((lock_value & kWaitTimeMask) == 0) {
|
|
// Here, just "mark" that the thread is going to sleep. Don't store the
|
|
// lock wait time in the lock as that will cause the current lock
|
|
// owner to think it experienced contention.
|
|
if (lockword_.compare_exchange_strong(
|
|
lock_value, lock_value | kSpinLockSleeper,
|
|
std::memory_order_relaxed, std::memory_order_relaxed)) {
|
|
// Successfully transitioned to kSpinLockSleeper. Pass
|
|
// kSpinLockSleeper to the SpinLockWait routine to properly indicate
|
|
// the last lock_value observed.
|
|
lock_value |= kSpinLockSleeper;
|
|
} else if ((lock_value & kSpinLockHeld) == 0) {
|
|
// Lock is free again, so try and acquire it before sleeping. The
|
|
// new lock state will be the number of cycles this thread waited if
|
|
// this thread obtains the lock.
|
|
lock_value = TryLockInternal(lock_value, wait_cycles);
|
|
continue; // Skip the delay at the end of the loop.
|
|
}
|
|
}
|
|
|
|
base_internal::SchedulingMode scheduling_mode;
|
|
if ((lock_value & kSpinLockCooperative) != 0) {
|
|
scheduling_mode = base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL;
|
|
} else {
|
|
scheduling_mode = base_internal::SCHEDULE_KERNEL_ONLY;
|
|
}
|
|
// SpinLockDelay() calls into fiber scheduler, we need to see
|
|
// synchronization there to avoid false positives.
|
|
ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0);
|
|
// Wait for an OS specific delay.
|
|
base_internal::SpinLockDelay(&lockword_, lock_value, ++lock_wait_call_count,
|
|
scheduling_mode);
|
|
ABSL_TSAN_MUTEX_POST_DIVERT(this, 0);
|
|
// Spin again after returning from the wait routine to give this thread
|
|
// some chance of obtaining the lock.
|
|
lock_value = SpinLoop();
|
|
wait_cycles = EncodeWaitCycles(wait_start_time, CycleClock::Now());
|
|
lock_value = TryLockInternal(lock_value, wait_cycles);
|
|
}
|
|
}
|
|
|
|
void SpinLock::SlowUnlock(uint32_t lock_value) {
|
|
base_internal::SpinLockWake(&lockword_,
|
|
false); // wake waiter if necessary
|
|
|
|
// If our acquisition was contended, collect contentionz profile info. We
|
|
// reserve a unitary wait time to represent that a waiter exists without our
|
|
// own acquisition having been contended.
|
|
if ((lock_value & kWaitTimeMask) != kSpinLockSleeper) {
|
|
const uint64_t wait_cycles = DecodeWaitCycles(lock_value);
|
|
ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0);
|
|
submit_profile_data(this, wait_cycles);
|
|
ABSL_TSAN_MUTEX_POST_DIVERT(this, 0);
|
|
}
|
|
}
|
|
|
|
// We use the upper 29 bits of the lock word to store the time spent waiting to
|
|
// acquire this lock. This is reported by contentionz profiling. Since the
|
|
// lower bits of the cycle counter wrap very quickly on high-frequency
|
|
// processors we divide to reduce the granularity to 2^kProfileTimestampShift
|
|
// sized units. On a 4Ghz machine this will lose track of wait times greater
|
|
// than (2^29/4 Ghz)*128 =~ 17.2 seconds. Such waits should be extremely rare.
|
|
static constexpr int kProfileTimestampShift = 7;
|
|
|
|
// We currently reserve the lower 3 bits.
|
|
static constexpr int kLockwordReservedShift = 3;
|
|
|
|
uint32_t SpinLock::EncodeWaitCycles(int64_t wait_start_time,
|
|
int64_t wait_end_time) {
|
|
static const int64_t kMaxWaitTime =
|
|
std::numeric_limits<uint32_t>::max() >> kLockwordReservedShift;
|
|
int64_t scaled_wait_time =
|
|
(wait_end_time - wait_start_time) >> kProfileTimestampShift;
|
|
|
|
// Return a representation of the time spent waiting that can be stored in
|
|
// the lock word's upper bits.
|
|
uint32_t clamped = static_cast<uint32_t>(
|
|
std::min(scaled_wait_time, kMaxWaitTime) << kLockwordReservedShift);
|
|
|
|
if (clamped == 0) {
|
|
return kSpinLockSleeper; // Just wake waiters, but don't record contention.
|
|
}
|
|
// Bump up value if necessary to avoid returning kSpinLockSleeper.
|
|
const uint32_t kMinWaitTime =
|
|
kSpinLockSleeper + (1 << kLockwordReservedShift);
|
|
if (clamped == kSpinLockSleeper) {
|
|
return kMinWaitTime;
|
|
}
|
|
return clamped;
|
|
}
|
|
|
|
uint64_t SpinLock::DecodeWaitCycles(uint32_t lock_value) {
|
|
// Cast to uint32_t first to ensure bits [63:32] are cleared.
|
|
const uint64_t scaled_wait_time =
|
|
static_cast<uint32_t>(lock_value & kWaitTimeMask);
|
|
return scaled_wait_time << (kProfileTimestampShift - kLockwordReservedShift);
|
|
}
|
|
|
|
} // namespace base_internal
|
|
ABSL_NAMESPACE_END
|
|
} // namespace absl
|