Introducing barrier to improve suspend-all performance

To improve the performance of suspend-all framework, the logic of suspend-all should be refactored.
Introduce barrier mechanism to synchronize the mutator threads when suspending-gc instead of via read-write-lock.

Issue: https://gitee.com/openharmony/arkcompiler_ets_runtime/issues/I9IRJZ

Signed-off-by: wuzhefeng <wuzhefeng1@huawei.com>
Change-Id: I09d374c1276a8d39888739179ebd908c984812d0
This commit is contained in:
wuzhefeng 2024-04-22 21:50:32 +08:00
parent 01c7b66679
commit a6c0d7989b
7 changed files with 221 additions and 49 deletions

View File

@ -681,6 +681,28 @@ void JSThread::TerminateExecution()
SetException(error.GetTaggedValue());
}
void JSThread::CheckAndPassActiveBarrier()
{
ThreadStateAndFlags oldStateAndFlags;
oldStateAndFlags.asInt = glueData_.stateAndFlags_.asInt;
if ((oldStateAndFlags.asStruct.flags & ThreadFlag::ACTIVE_BARRIER) != 0) {
PassSuspendBarrier();
}
}
bool JSThread::PassSuspendBarrier()
{
// Use suspendLock_ to avoid data-race between suspend-all-thread and suspended-threads.
LockHolder lock(suspendLock_);
if (suspendBarrier_ != nullptr) {
suspendBarrier_->PassStrongly();
suspendBarrier_ = nullptr;
ClearFlag(ThreadFlag::ACTIVE_BARRIER);
return true;
}
return false;
}
bool JSThread::CheckSafepoint()
{
ResetCheckSafePointStatus();
@ -691,7 +713,8 @@ bool JSThread::CheckSafepoint()
SetTerminationRequest(false);
}
if (IsSuspended()) {
if (HasSuspendRequest()) {
CheckAndPassActiveBarrier();
WaitSuspension();
}
@ -1006,21 +1029,29 @@ void JSThread::UpdateState(ThreadState newState)
TransferToRunning();
} else {
// Here can be some extra checks...
StoreState(newState, false);
StoreState(newState);
}
}
void JSThread::SuspendThread(bool internalSuspend)
void JSThread::SuspendThread(bool internalSuspend, SuspendBarrier* barrier)
{
LockHolder lock(suspendLock_);
if (!internalSuspend) {
// do smth here if we want to combine internal and external suspension
}
uint32_t old_count = suspendCount_++;
if (old_count == 0) {
SetFlag(ThreadFlag::SUSPEND_REQUEST);
SetCheckSafePointStatus();
}
if (barrier != nullptr) {
ASSERT(suspendBarrier_ == nullptr);
suspendBarrier_ = barrier;
SetFlag(ThreadFlag::ACTIVE_BARRIER);
SetCheckSafePointStatus();
}
}
void JSThread::ResumeThread(bool internalSuspend)
@ -1049,8 +1080,9 @@ void JSThread::WaitSuspension()
while (suspendCount_ > 0) {
suspendCondVar_.TimedWait(&suspendLock_, TIMEOUT);
// we need to do smth if Runtime is terminating at this point
LOG_ECMA(ERROR) << "Suspend timeout when triggering shared-gc: " << TIMEOUT << "ms";
}
ASSERT(!IsSuspended());
ASSERT(!HasSuspendRequest());
}
UpdateState(oldState);
}
@ -1070,15 +1102,14 @@ void JSThread::ManagedCodeEnd()
void JSThread::TransferFromRunningToSuspended(ThreadState newState)
{
ASSERT(currentThread == this);
StoreState(newState, false);
ASSERT(Runtime::GetInstance()->GetMutatorLock()->HasLock());
Runtime::GetInstance()->GetMutatorLock()->Unlock();
StoreSuspendedState(newState);
CheckAndPassActiveBarrier();
}
void JSThread::TransferToRunning()
{
ASSERT(currentThread == this);
StoreState(ThreadState::RUNNING, true);
StoreRunningState(ThreadState::RUNNING);
// Invoke free weak global callback when thread switch to running
if (!weakNodeFreeGlobalCallbacks_.empty()) {
InvokeWeakNodeFreeGlobalCallBack();
@ -1091,36 +1122,62 @@ void JSThread::TransferToRunning()
}
}
void JSThread::StoreState(ThreadState newState, bool lockMutatorLock)
inline void JSThread::StoreState(ThreadState newState)
{
while (true) {
ThreadStateAndFlags oldStateAndFlags;
oldStateAndFlags.asInt = glueData_.stateAndFlags_.asInt;
if (lockMutatorLock && oldStateAndFlags.asStruct.flags != ThreadFlag::NO_FLAGS) {
WaitSuspension();
continue;
}
ThreadStateAndFlags newStateAndFlags;
newStateAndFlags.asStruct.flags = oldStateAndFlags.asStruct.flags;
newStateAndFlags.asStruct.state = newState;
if (lockMutatorLock) {
Runtime::GetInstance()->GetMutatorLock()->ReadLock();
}
if (glueData_.stateAndFlags_.asAtomicInt.compare_exchange_weak(oldStateAndFlags.asNonvolatileInt,
newStateAndFlags.asNonvolatileInt, std::memory_order_release)) {
bool done = glueData_.stateAndFlags_.asAtomicInt.compare_exchange_weak(oldStateAndFlags.asNonvolatileInt,
newStateAndFlags.asNonvolatileInt,
std::memory_order_release);
if (LIKELY(done)) {
break;
}
}
}
// CAS failed. Unlock mutator lock
if (lockMutatorLock) {
ASSERT(Runtime::GetInstance()->GetMutatorLock()->HasLock());
Runtime::GetInstance()->GetMutatorLock()->Unlock();
void JSThread::StoreRunningState(ThreadState newState)
{
ASSERT(newState == ThreadState::RUNNING);
while (true) {
ThreadStateAndFlags oldStateAndFlags;
oldStateAndFlags.asInt = glueData_.stateAndFlags_.asInt;
ASSERT(oldStateAndFlags.asStruct.state != ThreadState::RUNNING);
if (LIKELY(oldStateAndFlags.asStruct.flags == ThreadFlag::NO_FLAGS)) {
ThreadStateAndFlags newStateAndFlags;
newStateAndFlags.asStruct.flags = oldStateAndFlags.asStruct.flags;
newStateAndFlags.asStruct.state = newState;
if (glueData_.stateAndFlags_.asAtomicInt.compare_exchange_weak(oldStateAndFlags.asNonvolatileInt,
newStateAndFlags.asNonvolatileInt,
std::memory_order_release)) {
break;
}
} else if ((oldStateAndFlags.asStruct.flags & ThreadFlag::ACTIVE_BARRIER) != 0) {
PassSuspendBarrier();
} else if ((oldStateAndFlags.asStruct.flags & ThreadFlag::SUSPEND_REQUEST) != 0) {
constexpr int TIMEOUT = 100;
LockHolder lock(suspendLock_);
while (suspendCount_ > 0) {
suspendCondVar_.TimedWait(&suspendLock_, TIMEOUT);
}
ASSERT(!HasSuspendRequest());
}
}
}
inline void JSThread::StoreSuspendedState(ThreadState newState)
{
ASSERT(newState != ThreadState::RUNNING);
StoreState(newState);
}
void JSThread::PostFork()
{
SetThreadId();
@ -1138,7 +1195,7 @@ void JSThread::PostFork()
bool JSThread::IsInManagedState() const
{
ASSERT(this == JSThread::GetCurrent());
return GetMutatorLockState() == MutatorLock::MutatorLockState::RDLOCK && GetState() == ThreadState::RUNNING;
return GetState() == ThreadState::RUNNING;
}
MutatorLock::MutatorLockState JSThread::GetMutatorLockState() const

View File

@ -75,10 +75,12 @@ enum class StableArrayChangeKind { PROTO, NOT_PROTO };
enum ThreadFlag : uint16_t {
NO_FLAGS = 0 << 0,
SUSPEND_REQUEST = 1 << 0
SUSPEND_REQUEST = 1 << 0,
ACTIVE_BARRIER = 1 << 1,
};
static constexpr uint32_t THREAD_STATE_OFFSET = 16;
static constexpr uint32_t THREAD_FLAGS_MASK = (0x1 << THREAD_STATE_OFFSET) - 1;
enum class ThreadState : uint16_t {
CREATED = 0,
RUNNING = 1,
@ -472,6 +474,10 @@ public:
bool CheckSafepoint();
void CheckAndPassActiveBarrier();
bool PassSuspendBarrier();
void SetGetStackSignal(bool isParseStack)
{
getStackSignal_ = isParseStack;
@ -1194,19 +1200,26 @@ public:
fullMarkRequest_ = true;
}
inline bool IsThreadSafe()
inline bool IsThreadSafe() const
{
return IsMainThread() || IsSuspended();
return IsMainThread() || HasSuspendRequest();
}
inline bool IsSuspended()
bool IsSuspended() const
{
bool f = ReadFlag(ThreadFlag::SUSPEND_REQUEST);
bool s = (GetState() != ThreadState::RUNNING);
return f && s;
}
inline bool HasSuspendRequest() const
{
return ReadFlag(ThreadFlag::SUSPEND_REQUEST);
}
void CheckSafepointIfSuspended()
{
if (IsSuspended()) {
if (HasSuspendRequest()) {
WaitSuspension();
}
}
@ -1224,7 +1237,7 @@ public:
return static_cast<enum ThreadState>(stateAndFlags >> THREAD_STATE_OFFSET);
}
void PUBLIC_API UpdateState(ThreadState newState);
void SuspendThread(bool internalSuspend);
void SuspendThread(bool internalSuspend, SuspendBarrier* barrier = nullptr);
void ResumeThread(bool internalSuspend);
void WaitSuspension();
static bool IsMainThread();
@ -1304,12 +1317,22 @@ private:
}
void TransferFromRunningToSuspended(ThreadState newState);
void TransferToRunning();
void StoreState(ThreadState newState, bool lockMutatorLock);
inline void StoreState(ThreadState newState);
void StoreRunningState(ThreadState newState);
void StoreSuspendedState(ThreadState newState);
bool ReadFlag(ThreadFlag flag) const
{
return (glueData_.stateAndFlags_.asStruct.flags & static_cast<uint16_t>(flag)) != 0;
uint32_t stateAndFlags = glueData_.stateAndFlags_.asAtomicInt.load(std::memory_order_acquire);
uint16_t flags = (stateAndFlags & THREAD_FLAGS_MASK);
return (flags & static_cast<uint16_t>(flag)) != 0;
}
void SetFlag(ThreadFlag flag)
{
glueData_.stateAndFlags_.asAtomicInt.fetch_or(flag, std::memory_order_seq_cst);
@ -1380,6 +1403,7 @@ private:
Mutex suspendLock_;
int32_t suspendCount_ {0};
ConditionVariable suspendCondVar_;
SuspendBarrier *suspendBarrier_ {nullptr};
bool isJitThread_ {false};
RecursiveMutex jitMutex_;

View File

@ -76,4 +76,30 @@ void MutatorLock::SetState(MutatorLock::MutatorLockState newState)
JSThread::GetCurrent()->SetMutatorLockState(newState);
}
#endif
void SuspendBarrier::Wait()
{
while (true) {
int32_t curCount = passBarrierCount_.load(std::memory_order_relaxed);
if (LIKELY(curCount > 0)) {
#if defined(PANDA_TARGET_OHOS)
sched_yield();
#endif
} else {
curCount = passBarrierCount_.load(std::memory_order_relaxed);
ASSERT(curCount == 0);
break;
}
}
}
void SuspendBarrier::PassCount(int32_t delta)
{
bool done = false;
do {
int32_t curCount = passBarrierCount_.load(std::memory_order_relaxed);
// Reduce value by 1.
done = passBarrierCount_.compare_exchange_strong(curCount, curCount + delta);
} while (!done);
}
} // namespace panda::ecmascript

View File

@ -19,6 +19,7 @@
#include "ecmascript/platform/mutex.h"
namespace panda::ecmascript {
class MutatorLock : public RWLock {
#ifndef NDEBUG
public:
@ -35,5 +36,44 @@ private:
void SetState(MutatorLockState newState);
#endif
};
class SuspendBarrier {
public:
SuspendBarrier() : passBarrierCount_(0)
{
}
explicit SuspendBarrier(int32_t count) : passBarrierCount_(count)
{
}
void Wait();
void Pass()
{
PassCount(-1);
}
void PassStrongly()
{
passBarrierCount_.fetch_sub(1, std::memory_order_seq_cst);
}
void Initialize(int32_t count)
{
passBarrierCount_.store(count, std::memory_order_relaxed);
}
void Increment(int32_t delta)
{
PassCount(delta);
Wait();
}
private:
void PassCount(int32_t delta);
std::atomic<int32_t> passBarrierCount_;
};
} // namespace panda::ecmascript
#endif // ECMASCRIPT_MUTATOR_LOCK_H

View File

@ -17,6 +17,7 @@
#define ECMASCRIPT_PLATFORM_MUTEX_H
#include <pthread.h>
#include <atomic>
#include "ecmascript/common.h"
#ifdef DEBUG

View File

@ -167,18 +167,46 @@ void Runtime::ResumeAll(JSThread *current)
void Runtime::SuspendAllThreadsImpl(JSThread *current)
{
LockHolder lock(threadsLock_);
while (suspendNewCount_ != 0) {
// Someone has already suspended all threads.
// Wait until it finishes.
threadSuspendCondVar_.Wait(&threadsLock_);
}
suspendNewCount_++;
for (auto i : threads_) {
if (i != current) {
i->SuspendThread(true);
SuspendBarrier barrier;
{
LockHolder lock(threadsLock_);
while (suspendNewCount_ != 0) {
// Someone has already suspended all threads.
// Wait until it finishes.
threadSuspendCondVar_.Wait(&threadsLock_);
}
suspendNewCount_++;
if (threads_.size() == 1) {
ASSERT(current == mainThread_);
return;
}
barrier.Initialize(threads_.size() - 1);
for (auto i: threads_) {
if (i == current) {
continue;
}
i->SuspendThread(+1, &barrier);
// The two flags, SUSPEND_REQUEST and ACTIVE_BARRIER, are set by Suspend-Thread guarded by suspendLock_, so
// the target thread-I may do not see these two flags in time. As a result, it can switch its state freely
// without responding to the ACTIVE_BARRIER flag and the suspend-thread will always wait it. However,
// as long as it sees the flags, the actions of passing barrier will be triggered. When the thread-I
// switches from NON_RUNNING to RUNNING, it will firstly pass the barrier and then be blocked by the
// SUSPEND_REQUEST flag. If the thread-I switches from RUNNING to NON_RUNNING, it will switch the state and
// then act on the barrier. If the thread-I go to checkpoint in RUNNING state, it will act on the barrier
// and be blocked by SUSPEND_REQUEST flag.
if (i->IsSuspended()) {
// Because of the multi-threads situation, currently thread-I may be in RUNNING state or is goding to
// be RUNNING state even inside this branch. In both scenarios, for instance of RUNNING state,
// according to the modification order of atomic-variable stateAndFlags_, thread-I will see the
// SUSPEND_REQUEST and ACTIVE_BARRIER and act on them before switching to RUNNING. Besides, notice the
// using of suspendLock_ inside PassSuspendBarrier(), there is not data-race for passing barrier.
i->PassSuspendBarrier();
}
}
}
barrier.Wait();
}
void Runtime::ResumeAllThreadsImpl(JSThread *current)

View File

@ -117,7 +117,7 @@ public:
{
bool result = true;
for (auto i: vms) {
result &= i->GetAssociatedJSThread()->IsSuspended();
result &= i->GetAssociatedJSThread()->HasSuspendRequest();
}
return result;
}
@ -265,17 +265,13 @@ HWTEST_F_L0(StateTransitioningTest, IsInRunningStateTest)
HWTEST_F_L0(StateTransitioningTest, ChangeStateTest)
{
ASSERT(Runtime::GetInstance()->GetMutatorLock()->HasLock());
{
ThreadNativeScope nativeScope(thread);
ASSERT(!Runtime::GetInstance()->GetMutatorLock()->HasLock());
}
{
ThreadNativeScope nativeScope(thread);
ASSERT(!Runtime::GetInstance()->GetMutatorLock()->HasLock());
{
ThreadManagedScope managedScope(thread);
ASSERT(Runtime::GetInstance()->GetMutatorLock()->HasLock());
}
}
}
@ -330,8 +326,8 @@ HWTEST_F_L0(StateTransitioningTest, SuspendAllNativeTransferToRunningTest)
SuspendAllScope suspendScope(JSThread::GetCurrent());
EXPECT_TRUE(CheckAllThreadsState(ThreadState::NATIVE));
ChangeAllThreadsToRunning();
while (!CheckAllThreadsState(ThreadState::IS_SUSPENDED)) {}
EXPECT_TRUE(CheckAllThreadsState(ThreadState::IS_SUSPENDED));
while (!CheckAllThreadsState(ThreadState::NATIVE)) {}
EXPECT_TRUE(CheckAllThreadsState(ThreadState::NATIVE));
}
while (CheckAllThreadsState(ThreadState::IS_SUSPENDED)) {}
while (CheckAllThreadsState(ThreadState::NATIVE)) {}