[wasm] Use shared mutex for background compile token
This introduces a new {base::SharedMutex}, which mimics {std::shared_mutex}, available in C++17. It is being used for synchronizing the WebAssembly background compile tasks. This removes a lot of unnecessary contention, leading to synchronization of background tasks that should be able to run in parallel ideally. Locally, this reduces Liftoff compilation time by more than 20 percent. R=mstarzinger@chromium.org, mlippautz@chromium.org Bug: chromium:924956 Cq-Include-Trybots: luci.v8.try:v8_linux64_tsan_rel Change-Id: I8c522faf385149bfe2cf00d777a7942c537f9cd2 Reviewed-on: https://chromium-review.googlesource.com/c/1477731 Commit-Queue: Clemens Hammacher <clemensh@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Michael Starzinger <mstarzinger@chromium.org> Cr-Commit-Position: refs/heads/master@{#59729}
This commit is contained in:
parent
02b9847f4e
commit
2664a259a9
@ -155,6 +155,45 @@ bool RecursiveMutex::TryLock() {
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedMutex::SharedMutex() { pthread_rwlock_init(&native_handle_, nullptr); }
|
||||
|
||||
SharedMutex::~SharedMutex() {
|
||||
int result = pthread_rwlock_destroy(&native_handle_);
|
||||
DCHECK_EQ(0, result);
|
||||
USE(result);
|
||||
}
|
||||
|
||||
void SharedMutex::LockShared() {
|
||||
int result = pthread_rwlock_rdlock(&native_handle_);
|
||||
DCHECK_EQ(0, result);
|
||||
USE(result);
|
||||
}
|
||||
|
||||
void SharedMutex::LockExclusive() {
|
||||
int result = pthread_rwlock_wrlock(&native_handle_);
|
||||
DCHECK_EQ(0, result);
|
||||
USE(result);
|
||||
}
|
||||
|
||||
void SharedMutex::UnlockShared() {
|
||||
int result = pthread_rwlock_unlock(&native_handle_);
|
||||
DCHECK_EQ(0, result);
|
||||
USE(result);
|
||||
}
|
||||
|
||||
void SharedMutex::UnlockExclusive() {
|
||||
// Same code as {UnlockShared} on POSIX.
|
||||
UnlockShared();
|
||||
}
|
||||
|
||||
bool SharedMutex::TryLockShared() {
|
||||
return pthread_rwlock_tryrdlock(&native_handle_) == 0;
|
||||
}
|
||||
|
||||
bool SharedMutex::TryLockExclusive() {
|
||||
return pthread_rwlock_trywrlock(&native_handle_) == 0;
|
||||
}
|
||||
|
||||
#elif V8_OS_WIN
|
||||
|
||||
Mutex::Mutex() : native_handle_(SRWLOCK_INIT) {
|
||||
@ -233,6 +272,28 @@ bool RecursiveMutex::TryLock() {
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedMutex::SharedMutex() : native_handle_(SRWLOCK_INIT) {}
|
||||
|
||||
SharedMutex::~SharedMutex() {}
|
||||
|
||||
void SharedMutex::LockShared() { AcquireSRWLockShared(&native_handle_); }
|
||||
|
||||
void SharedMutex::LockExclusive() { AcquireSRWLockExclusive(&native_handle_); }
|
||||
|
||||
void SharedMutex::UnlockShared() { ReleaseSRWLockShared(&native_handle_); }
|
||||
|
||||
void SharedMutex::UnlockExclusive() {
|
||||
ReleaseSRWLockExclusive(&native_handle_);
|
||||
}
|
||||
|
||||
bool SharedMutex::TryLockShared() {
|
||||
return TryAcquireSRWLockShared(&native_handle_);
|
||||
}
|
||||
|
||||
bool SharedMutex::TryLockExclusive() {
|
||||
return TryAcquireSRWLockExclusive(&native_handle_);
|
||||
}
|
||||
|
||||
#endif // V8_OS_POSIX
|
||||
|
||||
} // namespace base
|
||||
|
@ -150,6 +150,7 @@ class V8_BASE_EXPORT RecursiveMutex final {
|
||||
// successfully locked.
|
||||
bool TryLock() V8_WARN_UNUSED_RESULT;
|
||||
|
||||
private:
|
||||
// The implementation-defined native handle type.
|
||||
#if V8_OS_POSIX
|
||||
typedef pthread_mutex_t NativeHandle;
|
||||
@ -157,7 +158,6 @@ class V8_BASE_EXPORT RecursiveMutex final {
|
||||
typedef CRITICAL_SECTION NativeHandle;
|
||||
#endif
|
||||
|
||||
private:
|
||||
NativeHandle native_handle_;
|
||||
#ifdef DEBUG
|
||||
int level_;
|
||||
@ -183,6 +183,73 @@ typedef LazyStaticInstance<RecursiveMutex,
|
||||
|
||||
#define LAZY_RECURSIVE_MUTEX_INITIALIZER LAZY_STATIC_INSTANCE_INITIALIZER
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// SharedMutex - a replacement for std::shared_mutex
|
||||
//
|
||||
// This class is a synchronization primitive that can be used to protect shared
|
||||
// data from being simultaneously accessed by multiple threads. In contrast to
|
||||
// other mutex types which facilitate exclusive access, a shared_mutex has two
|
||||
// levels of access:
|
||||
// - shared: several threads can share ownership of the same mutex.
|
||||
// - exclusive: only one thread can own the mutex.
|
||||
// Shared mutexes are usually used in situations when multiple readers can
|
||||
// access the same resource at the same time without causing data races, but
|
||||
// only one writer can do so.
|
||||
// The SharedMutex class is non-copyable.
|
||||
|
||||
class V8_BASE_EXPORT SharedMutex final {
|
||||
public:
|
||||
SharedMutex();
|
||||
~SharedMutex();
|
||||
|
||||
// Acquires shared ownership of the {SharedMutex}. If another thread is
|
||||
// holding the mutex in exclusive ownership, a call to {LockShared()} will
|
||||
// block execution until shared ownership can be acquired.
|
||||
// If {LockShared()} is called by a thread that already owns the mutex in any
|
||||
// mode (exclusive or shared), the behavior is undefined.
|
||||
void LockShared();
|
||||
|
||||
// Locks the SharedMutex. If another thread has already locked the mutex, a
|
||||
// call to {LockExclusive()} will block execution until the lock is acquired.
|
||||
// If {LockExclusive()} is called by a thread that already owns the mutex in
|
||||
// any mode (shared or exclusive), the behavior is undefined.
|
||||
void LockExclusive();
|
||||
|
||||
// Releases the {SharedMutex} from shared ownership by the calling thread.
|
||||
// The mutex must be locked by the current thread of execution in shared mode,
|
||||
// otherwise, the behavior is undefined.
|
||||
void UnlockShared();
|
||||
|
||||
// Unlocks the {SharedMutex}. It must be locked by the current thread of
|
||||
// execution, otherwise, the behavior is undefined.
|
||||
void UnlockExclusive();
|
||||
|
||||
// Tries to lock the {SharedMutex} in shared mode. Returns immediately. On
|
||||
// successful lock acquisition returns true, otherwise returns false.
|
||||
// This function is allowed to fail spuriously and return false even if the
|
||||
// mutex is not currenly exclusively locked by any other thread.
|
||||
bool TryLockShared() V8_WARN_UNUSED_RESULT;
|
||||
|
||||
// Tries to lock the {SharedMutex}. Returns immediately. On successful lock
|
||||
// acquisition returns true, otherwise returns false.
|
||||
// This function is allowed to fail spuriously and return false even if the
|
||||
// mutex is not currently locked by any other thread.
|
||||
// If try_lock is called by a thread that already owns the mutex in any mode
|
||||
// (shared or exclusive), the behavior is undefined.
|
||||
bool TryLockExclusive() V8_WARN_UNUSED_RESULT;
|
||||
|
||||
private:
|
||||
// The implementation-defined native handle type.
|
||||
#if V8_OS_POSIX
|
||||
typedef pthread_rwlock_t NativeHandle;
|
||||
#elif V8_OS_WIN
|
||||
typedef SRWLOCK NativeHandle;
|
||||
#endif
|
||||
|
||||
NativeHandle native_handle_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SharedMutex);
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LockGuard
|
||||
@ -222,6 +289,41 @@ class LockGuard final {
|
||||
|
||||
using MutexGuard = LockGuard<Mutex>;
|
||||
|
||||
enum MutexSharedType : bool { kShared = true, kExclusive = false };
|
||||
|
||||
template <MutexSharedType kIsShared,
|
||||
NullBehavior Behavior = NullBehavior::kRequireNotNull>
|
||||
class SharedMutexGuard final {
|
||||
public:
|
||||
explicit SharedMutexGuard(SharedMutex* mutex) : mutex_(mutex) {
|
||||
if (!has_mutex()) return;
|
||||
if (kIsShared) {
|
||||
mutex_->LockShared();
|
||||
} else {
|
||||
mutex_->LockExclusive();
|
||||
}
|
||||
}
|
||||
~SharedMutexGuard() {
|
||||
if (!has_mutex()) return;
|
||||
if (kIsShared) {
|
||||
mutex_->UnlockShared();
|
||||
} else {
|
||||
mutex_->UnlockExclusive();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SharedMutex* const mutex_;
|
||||
|
||||
bool V8_INLINE has_mutex() const {
|
||||
DCHECK_IMPLIES(Behavior == NullBehavior::kRequireNotNull,
|
||||
mutex_ != nullptr);
|
||||
return Behavior == NullBehavior::kRequireNotNull || mutex_ != nullptr;
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SharedMutexGuard);
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
} // namespace v8
|
||||
|
||||
|
@ -65,24 +65,21 @@ class BackgroundCompileToken {
|
||||
: native_module_(native_module) {}
|
||||
|
||||
void Cancel() {
|
||||
base::MutexGuard mutex_guard(&mutex_);
|
||||
base::SharedMutexGuard<base::kExclusive> mutex_guard(&mutex_);
|
||||
native_module_ = nullptr;
|
||||
}
|
||||
|
||||
// Only call this while holding the {mutex_}.
|
||||
void CancelLocked() { native_module_ = nullptr; }
|
||||
|
||||
private:
|
||||
friend class BackgroundCompileScope;
|
||||
base::Mutex mutex_;
|
||||
base::SharedMutex mutex_;
|
||||
NativeModule* native_module_;
|
||||
|
||||
NativeModule* StartScope() {
|
||||
mutex_.Lock();
|
||||
mutex_.LockShared();
|
||||
return native_module_;
|
||||
}
|
||||
|
||||
void ExitScope() { mutex_.Unlock(); }
|
||||
void ExitScope() { mutex_.UnlockShared(); }
|
||||
};
|
||||
|
||||
class CompilationStateImpl;
|
||||
@ -681,6 +678,7 @@ class BackgroundCompileTask : public CancelableTask {
|
||||
&env.value(), wire_bytes, async_counters_.get(), &detected_features);
|
||||
|
||||
// Step 3 (synchronized): Publish the compilation result.
|
||||
bool cancel_compilation = false;
|
||||
{
|
||||
BackgroundCompileScope compile_scope(token_);
|
||||
if (compile_scope.cancelled()) return;
|
||||
@ -690,9 +688,8 @@ class BackgroundCompileTask : public CancelableTask {
|
||||
compile_scope.compilation_state()->OnBackgroundTaskStopped(
|
||||
detected_features);
|
||||
// Also, cancel all remaining compilation.
|
||||
token_->CancelLocked();
|
||||
return;
|
||||
}
|
||||
cancel_compilation = true;
|
||||
} else {
|
||||
compile_scope.compilation_state()->OnFinishedUnit(
|
||||
unit->requested_tier(), code);
|
||||
if (deadline < MonotonicallyIncreasingTimeInMs()) {
|
||||
@ -703,6 +700,11 @@ class BackgroundCompileTask : public CancelableTask {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cancel_compilation) {
|
||||
token_->Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
UNREACHABLE(); // Loop exits via explicit return.
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user