/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 * * http://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. */ #pragma once #include <cassert> #include <climits> #include <cstdint> #include <utility> #include <folly/Optional.h> #include <folly/Portability.h> #include <folly/detail/Futex.h> namespace folly { /** * Tiny exclusive lock that uses 2 bits. It is stored as 1 byte and * has APIs for using the remaining 6 bits for storing user data. * * 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, * 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.) * * Unused bits in the lock can be used to store user data via * lockAndLoad() and unlockAndStore(), or LockGuardWithData. * * 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 @mode/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. * */ class MicroLockCore { … }; inline detail::Futex<>* MicroLockCore::word() const noexcept { … } inline unsigned MicroLockCore::baseShift() const noexcept { … } inline unsigned MicroLockCore::heldBit() const noexcept { … } inline unsigned MicroLockCore::waitBit() const noexcept { … } inline void MicroLockCore::store( uint8_t value, std::memory_order order) noexcept { … } template <typename Func> void MicroLockCore::unlockAndStoreWithModifier(Func modifier) noexcept { … } inline void MicroLockCore::unlockAndStore(uint8_t value) noexcept { … } inline void MicroLockCore::unlock() noexcept { … } template <unsigned MaxSpins = 1000, unsigned MaxYields = 0> class MicroLockBase : public MicroLockCore { … }; template <unsigned MaxSpins, unsigned MaxYields> bool MicroLockBase<MaxSpins, MaxYields>::try_lock() noexcept { … } template <unsigned MaxSpins, unsigned MaxYields> uint8_t MicroLockBase<MaxSpins, MaxYields>::lockAndLoad() noexcept { … } MicroLock; } // namespace folly