mirror of
https://gitee.com/openharmony/arkcompiler_ets_runtime
synced 2024-11-26 19:50:55 +00:00
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:
parent
01c7b66679
commit
a6c0d7989b
@ -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
|
||||
|
@ -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_;
|
||||
|
@ -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
|
@ -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
|
@ -17,6 +17,7 @@
|
||||
#define ECMASCRIPT_PLATFORM_MUTEX_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <atomic>
|
||||
|
||||
#include "ecmascript/common.h"
|
||||
#ifdef DEBUG
|
||||
|
@ -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)
|
||||
|
@ -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)) {}
|
||||
|
Loading…
Reference in New Issue
Block a user