[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;
|
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
|
#elif V8_OS_WIN
|
||||||
|
|
||||||
Mutex::Mutex() : native_handle_(SRWLOCK_INIT) {
|
Mutex::Mutex() : native_handle_(SRWLOCK_INIT) {
|
||||||
@ -233,6 +272,28 @@ bool RecursiveMutex::TryLock() {
|
|||||||
return true;
|
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
|
#endif // V8_OS_POSIX
|
||||||
|
|
||||||
} // namespace base
|
} // namespace base
|
||||||
|
@ -150,6 +150,7 @@ class V8_BASE_EXPORT RecursiveMutex final {
|
|||||||
// successfully locked.
|
// successfully locked.
|
||||||
bool TryLock() V8_WARN_UNUSED_RESULT;
|
bool TryLock() V8_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
|
private:
|
||||||
// The implementation-defined native handle type.
|
// The implementation-defined native handle type.
|
||||||
#if V8_OS_POSIX
|
#if V8_OS_POSIX
|
||||||
typedef pthread_mutex_t NativeHandle;
|
typedef pthread_mutex_t NativeHandle;
|
||||||
@ -157,7 +158,6 @@ class V8_BASE_EXPORT RecursiveMutex final {
|
|||||||
typedef CRITICAL_SECTION NativeHandle;
|
typedef CRITICAL_SECTION NativeHandle;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
|
||||||
NativeHandle native_handle_;
|
NativeHandle native_handle_;
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
int level_;
|
int level_;
|
||||||
@ -183,6 +183,73 @@ typedef LazyStaticInstance<RecursiveMutex,
|
|||||||
|
|
||||||
#define LAZY_RECURSIVE_MUTEX_INITIALIZER LAZY_STATIC_INSTANCE_INITIALIZER
|
#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
|
// LockGuard
|
||||||
@ -222,6 +289,41 @@ class LockGuard final {
|
|||||||
|
|
||||||
using MutexGuard = LockGuard<Mutex>;
|
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 base
|
||||||
} // namespace v8
|
} // namespace v8
|
||||||
|
|
||||||
|
@ -65,24 +65,21 @@ class BackgroundCompileToken {
|
|||||||
: native_module_(native_module) {}
|
: native_module_(native_module) {}
|
||||||
|
|
||||||
void Cancel() {
|
void Cancel() {
|
||||||
base::MutexGuard mutex_guard(&mutex_);
|
base::SharedMutexGuard<base::kExclusive> mutex_guard(&mutex_);
|
||||||
native_module_ = nullptr;
|
native_module_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only call this while holding the {mutex_}.
|
|
||||||
void CancelLocked() { native_module_ = nullptr; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class BackgroundCompileScope;
|
friend class BackgroundCompileScope;
|
||||||
base::Mutex mutex_;
|
base::SharedMutex mutex_;
|
||||||
NativeModule* native_module_;
|
NativeModule* native_module_;
|
||||||
|
|
||||||
NativeModule* StartScope() {
|
NativeModule* StartScope() {
|
||||||
mutex_.Lock();
|
mutex_.LockShared();
|
||||||
return native_module_;
|
return native_module_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExitScope() { mutex_.Unlock(); }
|
void ExitScope() { mutex_.UnlockShared(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class CompilationStateImpl;
|
class CompilationStateImpl;
|
||||||
@ -681,6 +678,7 @@ class BackgroundCompileTask : public CancelableTask {
|
|||||||
&env.value(), wire_bytes, async_counters_.get(), &detected_features);
|
&env.value(), wire_bytes, async_counters_.get(), &detected_features);
|
||||||
|
|
||||||
// Step 3 (synchronized): Publish the compilation result.
|
// Step 3 (synchronized): Publish the compilation result.
|
||||||
|
bool cancel_compilation = false;
|
||||||
{
|
{
|
||||||
BackgroundCompileScope compile_scope(token_);
|
BackgroundCompileScope compile_scope(token_);
|
||||||
if (compile_scope.cancelled()) return;
|
if (compile_scope.cancelled()) return;
|
||||||
@ -690,18 +688,22 @@ class BackgroundCompileTask : public CancelableTask {
|
|||||||
compile_scope.compilation_state()->OnBackgroundTaskStopped(
|
compile_scope.compilation_state()->OnBackgroundTaskStopped(
|
||||||
detected_features);
|
detected_features);
|
||||||
// Also, cancel all remaining compilation.
|
// Also, cancel all remaining compilation.
|
||||||
token_->CancelLocked();
|
cancel_compilation = true;
|
||||||
return;
|
} else {
|
||||||
}
|
compile_scope.compilation_state()->OnFinishedUnit(
|
||||||
compile_scope.compilation_state()->OnFinishedUnit(
|
unit->requested_tier(), code);
|
||||||
unit->requested_tier(), code);
|
if (deadline < MonotonicallyIncreasingTimeInMs()) {
|
||||||
if (deadline < MonotonicallyIncreasingTimeInMs()) {
|
compile_scope.compilation_state()->ReportDetectedFeatures(
|
||||||
compile_scope.compilation_state()->ReportDetectedFeatures(
|
detected_features);
|
||||||
detected_features);
|
compile_scope.compilation_state()->RestartBackgroundCompileTask();
|
||||||
compile_scope.compilation_state()->RestartBackgroundCompileTask();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cancel_compilation) {
|
||||||
|
token_->Cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
UNREACHABLE(); // Loop exits via explicit return.
|
UNREACHABLE(); // Loop exits via explicit return.
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user