mirrored from https://chromium.googlesource.com/angle/angle
-
Notifications
You must be signed in to change notification settings - Fork 622
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement a lock/unlock-only mutex based on futex
Pthread mutexes are expensive due to their numerous features. When a mutex only needs to support lock and unlock, its implementation can be much simpler. The implementation in this change is "inspired" by a similar mutex in mesa. Expected uses of this mutex are: - Allowing some OpenGL calls to avoid the share group lock and instead lock the specific shared object they operate on. - Replacing SpinLock in the OpenCL implementation (spin-lock in user space is a bad idea [1]) - Generally anywhere we use std::mutex just to do lock/unlock Tests based on patch authored by Igor Nazarov <[email protected]> [1]:https://www.realworldtech.com/forum/?threadid=189711&curpostid=189723 Bug: angleproject:8667 Change-Id: I52278c9d19616338c499bbcef6684746caead6ca Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/5446558 Reviewed-by: Roman Lavrov <[email protected]> Commit-Queue: Shahbaz Youssefi <[email protected]>
- Loading branch information
Showing
6 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// | ||
// Copyright 2024 The ANGLE Project Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
// | ||
// SimpleMutex.cpp: | ||
// Implementation of SimpleMutex.h. | ||
|
||
#include "common/SimpleMutex.h" | ||
|
||
#if ANGLE_USE_FUTEX | ||
|
||
# include <limits.h> | ||
# include <stdint.h> | ||
|
||
# if defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) | ||
# include <linux/futex.h> | ||
# include <sys/syscall.h> | ||
# include <unistd.h> | ||
# endif // defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) | ||
|
||
# if defined(ANGLE_PLATFORM_WINDOWS) | ||
# include <errno.h> | ||
# include <windows.h> | ||
# endif // defined(ANGLE_PLATFORM_WINDOWS) | ||
|
||
namespace angle | ||
{ | ||
namespace priv | ||
{ | ||
# if defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) | ||
namespace | ||
{ | ||
ANGLE_INLINE void SysFutex(void *addr, int op, int val, int val3) | ||
{ | ||
syscall(SYS_futex, addr, op, val, nullptr, nullptr, val3); | ||
} | ||
} // anonymous namespace | ||
|
||
void MutexOnFutex::futexWait() | ||
{ | ||
SysFutex(&mState, FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG, kBlocked, FUTEX_BITSET_MATCH_ANY); | ||
} | ||
void MutexOnFutex::futexWake() | ||
{ | ||
SysFutex(&mState, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, kLocked, 0); | ||
} | ||
# endif // defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) | ||
|
||
# if defined(ANGLE_PLATFORM_WINDOWS) | ||
void MutexOnFutex::futexWait() | ||
{ | ||
int value = kBlocked; | ||
WaitOnAddress(&mState, &value, sizeof(value), INFINITE); | ||
} | ||
|
||
void MutexOnFutex::futexWake() | ||
{ | ||
WakeByAddressSingle(&mState); | ||
} | ||
# endif // defined(ANGLE_PLATFORM_WINDOWS) | ||
} // namespace priv | ||
} // namespace angle | ||
|
||
#endif // ANGLE_USE_FUTEX |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// | ||
// Copyright 2024 The ANGLE Project Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
// | ||
// SimpleMutex.h: | ||
// A simple non-recursive mutex that only supports lock and unlock operations. As such, it can be | ||
// implemented more efficiently than a generic mutex such as std::mutex. In the uncontended | ||
// paths, the implementation boils down to basically an inlined atomic operation and an untaken | ||
// branch. The implementation in this file is inspired by Mesa's src/util/simple_mtx.h, which in | ||
// turn is based on "mutex3" in: | ||
// | ||
// "Futexes Are Tricky" | ||
// http://www.akkadia.org/drepper/futex.pdf | ||
// | ||
// Given that std::condition_variable only interacts with std::mutex, SimpleMutex cannot be used | ||
// with condition variables. | ||
// | ||
|
||
#ifndef COMMON_SIMPLEMUTEX_H_ | ||
#define COMMON_SIMPLEMUTEX_H_ | ||
|
||
#include "common/debug.h" | ||
#include "common/platform.h" | ||
|
||
#include <atomic> | ||
#include <mutex> | ||
|
||
// Enable futexes on: | ||
// | ||
// - Linux and derivatives (Android, ChromeOS, etc) | ||
// - Windows 8+ | ||
// | ||
// There is no TSAN support for futex currently, so it is disabled in that case | ||
#if !defined(ANGLE_WITH_TSAN) | ||
# if defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) | ||
// Linux has had futexes for a very long time. Assume support. | ||
# define ANGLE_USE_FUTEX 1 | ||
# elif defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_ENABLE_WINDOWS_UWP) && \ | ||
!defined(ANGLE_WINDOWS_NO_FUTEX) | ||
// Windows has futexes since version 8, which is already end of life (let alone older versions). | ||
// Assume support. | ||
# define ANGLE_USE_FUTEX 1 | ||
# endif // defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID) | ||
#endif // !defined(ANGLE_WITH_TSAN) | ||
|
||
namespace angle | ||
{ | ||
namespace priv | ||
{ | ||
#if ANGLE_USE_FUTEX | ||
class MutexOnFutex | ||
{ | ||
public: | ||
void lock() | ||
{ | ||
uint32_t oldState = kUnlocked; | ||
const bool lockTaken = mState.compare_exchange_strong(oldState, kLocked); | ||
|
||
// In uncontended cases, the lock is acquired and there's nothing to do | ||
if (ANGLE_UNLIKELY(!lockTaken)) | ||
{ | ||
ASSERT(oldState == kLocked || oldState == kBlocked); | ||
|
||
// If not already marked as such, signal that the mutex is contended. | ||
if (oldState != kBlocked) | ||
{ | ||
oldState = mState.exchange(kBlocked, std::memory_order_acq_rel); | ||
} | ||
// Wait until the lock is acquired | ||
while (oldState != kUnlocked) | ||
{ | ||
futexWait(); | ||
oldState = mState.exchange(kBlocked, std::memory_order_acq_rel); | ||
} | ||
} | ||
} | ||
void unlock() | ||
{ | ||
// Unlock the mutex | ||
const uint32_t oldState = mState.fetch_add(-1, std::memory_order_acq_rel); | ||
|
||
// If another thread is waiting on this mutex, wake it up | ||
if (ANGLE_UNLIKELY(oldState != kLocked)) | ||
{ | ||
mState.store(kUnlocked, std::memory_order_relaxed); | ||
futexWake(); | ||
} | ||
} | ||
void assertLocked() { ASSERT(mState.load(std::memory_order_relaxed) != kUnlocked); } | ||
|
||
private: | ||
void futexWait(); | ||
void futexWake(); | ||
|
||
// Note: the ordering of these values is important due to |unlock()|'s atomic decrement. | ||
static constexpr uint32_t kUnlocked = 0; | ||
static constexpr uint32_t kLocked = 1; | ||
static constexpr uint32_t kBlocked = 2; | ||
|
||
std::atomic_uint32_t mState = 0; | ||
}; | ||
#else // !ANGLE_USE_FUTEX | ||
class MutexOnStd | ||
{ | ||
public: | ||
void lock() { mutex.lock(); } | ||
void unlock() { mutex.unlock(); } | ||
void assertLocked() { ASSERT(isLocked()); } | ||
|
||
private: | ||
bool isLocked() | ||
{ | ||
// This works because angle::SimpleMutex does not support recursion | ||
const bool acquiredLock = mutex.try_lock(); | ||
if (acquiredLock) | ||
{ | ||
mutex.unlock(); | ||
} | ||
|
||
return !acquiredLock; | ||
} | ||
|
||
std::mutex mutex; | ||
}; | ||
#endif // ANGLE_USE_FUTEX | ||
} // namespace priv | ||
|
||
#if ANGLE_USE_FUTEX | ||
using SimpleMutex = priv::MutexOnFutex; | ||
#else | ||
using SimpleMutex = priv::MutexOnStd; | ||
#endif | ||
|
||
} // namespace angle | ||
|
||
#endif // COMMON_SIMPLEMUTEX_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// | ||
// Copyright 2024 The ANGLE Project Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
// | ||
// SimpleMutex_unittest: | ||
// Tests of the SimpleMutex class | ||
// | ||
|
||
#include <gtest/gtest.h> | ||
|
||
#include "common/SimpleMutex.h" | ||
|
||
namespace angle | ||
{ | ||
namespace | ||
{ | ||
struct NoOpMutex | ||
{ | ||
void lock() {} | ||
void unlock() {} | ||
}; | ||
|
||
template <typename TestMutex> | ||
bool runBasicMutexTest() | ||
{ | ||
constexpr size_t kThreadCount = 16; | ||
constexpr size_t kIterationCount = 50'000; | ||
|
||
std::array<std::thread, kThreadCount> threads; | ||
|
||
std::mutex mutex; | ||
std::condition_variable condVar; | ||
size_t readyCount = 0; | ||
|
||
TestMutex testMutex; | ||
std::atomic<size_t> testVar; | ||
|
||
for (size_t i = 0; i < kThreadCount; ++i) | ||
{ | ||
threads[i] = std::thread([&]() { | ||
// Wait for all threads to start, so the following loop is as simultaneously executed as | ||
// possible. | ||
{ | ||
std::unique_lock<std::mutex> lock(mutex); | ||
++readyCount; | ||
if (readyCount < kThreadCount) | ||
{ | ||
condVar.wait(lock, [&]() { return readyCount == kThreadCount; }); | ||
} | ||
else | ||
{ | ||
condVar.notify_all(); | ||
} | ||
} | ||
for (size_t j = 0; j < kIterationCount; ++j) | ||
{ | ||
std::lock_guard<TestMutex> lock(testMutex); | ||
const int local = testVar.load(std::memory_order_relaxed); | ||
const int newValue = local + 1; | ||
testVar.store(newValue, std::memory_order_relaxed); | ||
} | ||
}); | ||
} | ||
|
||
for (size_t i = 0; i < kThreadCount; ++i) | ||
{ | ||
threads[i].join(); | ||
} | ||
|
||
const bool passed = testVar.load() == kThreadCount * kIterationCount; | ||
return passed; | ||
} | ||
} // anonymous namespace | ||
|
||
// Tests basic usage of std::mutex. | ||
TEST(MutexTest, BasicStdMutex) | ||
{ | ||
EXPECT_TRUE(runBasicMutexTest<std::mutex>()); | ||
} | ||
|
||
// Tests basic usage of angle::SimpleMutex. | ||
TEST(MutexTest, BasicSimpleMutex) | ||
{ | ||
EXPECT_TRUE(runBasicMutexTest<SimpleMutex>()); | ||
} | ||
|
||
// Tests failure with NoOpMutex. | ||
TEST(MutexTest, BasicNoOpMutex) | ||
{ | ||
// Technically not _guaranteed_ to calculate the wrong value, but highly likely to do so. | ||
EXPECT_FALSE(runBasicMutexTest<NoOpMutex>()); | ||
} | ||
} // namespace angle |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters