SWDEV-458943 - Implement std::mutex based monitor

Implement std::mutex based monitor that has much
simpler logics than legacy monitor.
Create DEBUG_CLR_USE_STDMUTEX_IN_AMD_MONITOR to
toggle them.
If DEBUG_CLR_USE_STDMUTEX_IN_AMD_MONITOR = false
  (by default), use legacy monitor;
If DEBUG_CLR_USE_STDMUTEX_IN_AMD_MONITOR = true,
  use std::mutex based monitor.
If no perf drop of stl::mutex based monitor,
legacy one will be removed later.

Change-Id: I1d21368ff462477d3238d71e4e2a1a7d6b9167ad
Tá an tiomantas seo le fáil i:
Tao Sang
2024-06-05 11:25:18 -04:00
tiomanta ag Tao Sang
tuismitheoir fa07c33cba
tiomantas 73c02041e1
D'athraigh 4 comhad le 228 breiseanna agus 100 scriosta
+1 -1
Féach ar an gComhad
@@ -183,7 +183,7 @@ class Event : public RuntimeObject {
//! Signal all threads waiting on this event.
void signal() {
ScopedLock lock(lock_);
ScopedLock lock(lock_);// Unnecessary
lock_.notifyAll();
}
+89 -1
Féach ar an gComhad
@@ -29,11 +29,14 @@
#include <utility>
namespace amd {
MonitorBase::~MonitorBase() {}
namespace legacy_monitor {
Monitor::Monitor(const char* name, bool recursive)
: contendersList_(0), onDeck_(0), waitersList_(NULL), owner_(NULL), recursive_(recursive) {
if (name == NULL) {
const char* unknownName = "@unknown@";
const char unknownName[] = "@unknown@";
assert(sizeof(unknownName) < sizeof(name_) && "just checking");
::strncpy(name_, unknownName, sizeof(name_) - 1);
} else {
@@ -316,4 +319,89 @@ void Monitor::notifyAll() {
}
}
bool Monitor::tryLock() {
Thread* thread = Thread::current();
assert(thread != NULL && "cannot lock() from (null)");
intptr_t ptr = contendersList_.load(std::memory_order_acquire);
if (unlikely((ptr & kLockBit) != 0)) {
if (recursive_ && thread == owner_) {
// Recursive lock: increment the lock count and return.
++lockCount_;
return true;
}
return false; // Already locked!
}
if (unlikely(!contendersList_.compare_exchange_weak(
ptr, ptr | kLockBit, std::memory_order_acq_rel, std::memory_order_acquire))) {
return false; // We failed the CAS from unlocked to locked.
}
setOwner(thread); // cannot move above the CAS.
lockCount_ = 1;
return true;
}
void Monitor::lock() {
if (unlikely(!tryLock())) {
// The lock is contented.
finishLock();
}
// This is the beginning of the critical region. From now-on, everything
// executes single-threaded!
//
}
void Monitor::unlock() {
assert(isLocked() && owner_ == Thread::current() && "invariant");
if (recursive_ && --lockCount_ > 0) {
// was a recursive lock case, simply return.
return;
}
setOwner(NULL);
// Clear the lock bit.
intptr_t ptr = contendersList_.load(std::memory_order_acquire);
while (!contendersList_.compare_exchange_weak(ptr, ptr & ~kLockBit, std::memory_order_acq_rel,
std::memory_order_acquire))
;
// A StoreLoad barrier is required to make sure future loads do not happen before the
// contendersList_ store is published.
std::atomic_thread_fence(std::memory_order_seq_cst);
//
// We succeeded the CAS from locked to unlocked.
// This is the end of the critical region.
// Check if we have an on-deck thread that needs signaling.
intptr_t onDeck = onDeck_;
if (onDeck != 0) {
if ((onDeck & kLockBit) == 0) {
// Only signal if it is unmarked.
reinterpret_cast<Semaphore*>(onDeck)->post();
}
return; // We are done.
}
// We do not have an on-deck thread yet, we might have to walk the list in
// order to select the next onDeck_. Only one thread needs to fill onDeck_,
// so return if the list is empty or if the lock got acquired again (it's
// somebody else's problem now!)
intptr_t head = contendersList_;
if (head == 0 || (head & kLockBit) != 0) {
return;
}
// Finish the unlock operation: find a thread to wake up.
finishUnlock();
}
} // namespace legacy_monitor
} // namespace amd
+135 -97
Féach ar an gComhad
@@ -22,9 +22,11 @@
#define MONITOR_HPP_
#include "top.hpp"
#include "utils/flags.hpp"
#include "thread/semaphore.hpp"
#include "thread/thread.hpp"
#include <condition_variable>
#include <mutex>
#include <atomic>
#include <tuple>
#include <utility>
@@ -69,7 +71,20 @@ template <class T, class AllocClass = HeapObject> struct SimplyLinkedNode : publ
} // namespace details
class Monitor : public HeapObject {
class MonitorBase {
public:
virtual ~MonitorBase() = 0;
virtual bool tryLock() = 0;
virtual void lock() = 0;
virtual void unlock() = 0;
virtual void wait() = 0;
virtual void notify() = 0;
virtual void notifyAll() = 0;
virtual const char* name() const = 0;
};
namespace legacy_monitor {
class Monitor final: public HeapObject, public MonitorBase {
typedef details::SimplyLinkedNode<Semaphore*, StackObject> LinkedNode;
private:
@@ -124,13 +139,13 @@ class Monitor : public HeapObject {
~Monitor() {}
//! Try to acquire the lock, return true if successful.
inline bool tryLock();
bool tryLock();
//! Acquire the lock or suspend the calling thread.
inline void lock();
void lock();
//! Release the lock and wake a single waiting thread if any.
inline void unlock();
void unlock();
/*! \brief Give up the lock and go to sleep.
*
@@ -155,10 +170,119 @@ class Monitor : public HeapObject {
const char* name() const { return name_; }
};
class ScopedLock : StackObject {
private:
Monitor* lock_;
} // namespace legacy_monitor
namespace mutex_monitor {
class Monitor final: public HeapObject, public MonitorBase {
public:
explicit Monitor(const char* name = nullptr, bool recursive = false)
: recursive_(recursive) {
if (name == NULL) {
const char unknownName[] = "@unknown@";
assert(sizeof(unknownName) < sizeof(name_) && "just checking");
::strncpy(name_, unknownName, sizeof(name_) - 1);
} else {
::strncpy(name_, name, sizeof(name_) - 1);
}
name_[sizeof(name_) - 1] = '\0';
if (recursive)
new (&rec_mutex_) std::recursive_mutex();
else
new (&mutex_) std::mutex();
}
~Monitor() {
// Caller must make sure the mutext is unlocked.
if (recursive_)
rec_mutex_.~recursive_mutex();
else
mutex_.~mutex();
}
//! Try to acquire the lock, return true if successful, false if failed.
bool tryLock() {
return recursive_ ? rec_mutex_.try_lock() : mutex_.try_lock();
}
//! Acquire the lock or suspend the calling thread.
void lock() {
recursive_ ? rec_mutex_.lock() : mutex_.lock();
}
//! Release the lock and wake a single waiting thread if any.
void unlock() {
recursive_ ? rec_mutex_.unlock() : mutex_.unlock();
}
/*! \brief Give up the lock and go to sleep.
*
* Calling wait() causes the current thread to go to sleep until
* another thread calls notify()/notifyAll().
*
* \note The monitor must be owned before calling wait().
*/
void wait() {
assert(recursive_ == false && "wait() doesn't support recursive mode");
// the mutex must be locked by caller
std::unique_lock lk(mutex_, std::adopt_lock);
cv_.wait(lk);
// the mutex is locked again
lk.release(); // Release the ownership so that the caller should unlock the mutex
}
/*! \brief Wake up a single thread waiting on this monitor.
*
* \note The monitor may or may not be owned before calling notify().
*/
void notify() { cv_.notify_one(); }
/*! \brief Wake up all threads that are waiting on this monitor.
*
* \note The monitor may or may not be owned before calling notifyAll().
*/
void notifyAll() { cv_.notify_all(); }
//! Return this lock's name.
const char* name() const { return name_; }
private:
union {
std::mutex mutex_;
std::recursive_mutex rec_mutex_;
};
std::condition_variable cv_; //!< The condition variable for sync on the mutex
char name_[64]; //!< The mutex's name
const bool recursive_; //!< True if this is a recursive mutex, false otherwise.
};
} // namespace mutex_monitor
// Monitor API wrapper to user
class Monitor {
public:
explicit Monitor(const char* name = nullptr, bool recursive = false) {
if (DEBUG_CLR_USE_STDMUTEX_IN_AMD_MONITOR) {
monitor_ = new mutex_monitor::Monitor(name, recursive);
}
else {
monitor_ = new legacy_monitor::Monitor(name, recursive);
}
}
inline ~Monitor() { delete monitor_; };
inline bool tryLock() { return monitor_->tryLock(); }
inline void lock() { monitor_->lock(); }
inline void unlock() { monitor_->unlock(); }
inline void wait() { monitor_->wait(); }
inline void notify() { monitor_->notify(); }
inline void notifyAll() { monitor_->notifyAll(); }
inline const char* name() { return monitor_->name(); }
private:
MonitorBase* monitor_;
};
class ScopedLock : StackObject {
public:
ScopedLock(Monitor& lock) : lock_(&lock) { lock_->lock(); }
@@ -169,97 +293,11 @@ class ScopedLock : StackObject {
~ScopedLock() {
if (lock_) lock_->unlock();
}
private:
Monitor* lock_;
};
/*! @}
* @}
*/
inline bool Monitor::tryLock() {
Thread* thread = Thread::current();
assert(thread != NULL && "cannot lock() from (null)");
intptr_t ptr = contendersList_.load(std::memory_order_acquire);
if (unlikely((ptr & kLockBit) != 0)) {
if (recursive_ && thread == owner_) {
// Recursive lock: increment the lock count and return.
++lockCount_;
return true;
}
return false; // Already locked!
}
if (unlikely(!contendersList_.compare_exchange_weak(
ptr, ptr | kLockBit, std::memory_order_acq_rel, std::memory_order_acquire))) {
return false; // We failed the CAS from unlocked to locked.
}
setOwner(thread); // cannot move above the CAS.
lockCount_ = 1;
return true;
}
inline void Monitor::lock() {
if (unlikely(!tryLock())) {
// The lock is contented.
finishLock();
}
// This is the beginning of the critical region. From now-on, everything
// executes single-threaded!
//
}
inline void Monitor::unlock() {
assert(isLocked() && owner_ == Thread::current() && "invariant");
if (recursive_ && --lockCount_ > 0) {
// was a recursive lock case, simply return.
return;
}
setOwner(NULL);
// Clear the lock bit.
intptr_t ptr = contendersList_.load(std::memory_order_acquire);
while (!contendersList_.compare_exchange_weak(ptr, ptr & ~kLockBit, std::memory_order_acq_rel,
std::memory_order_acquire))
;
// A StoreLoad barrier is required to make sure future loads do not happen before the
// contendersList_ store is published.
std::atomic_thread_fence(std::memory_order_seq_cst);
//
// We succeeded the CAS from locked to unlocked.
// This is the end of the critical region.
// Check if we have an on-deck thread that needs signaling.
intptr_t onDeck = onDeck_;
if (onDeck != 0) {
if ((onDeck & kLockBit) == 0) {
// Only signal if it is unmarked.
reinterpret_cast<Semaphore*>(onDeck)->post();
}
return; // We are done.
}
// We do not have an on-deck thread yet, we might have to walk the list in
// order to select the next onDeck_. Only one thread needs to fill onDeck_,
// so return if the list is empty or if the lock got acquired again (it's
// somebody else's problem now!)
intptr_t head = contendersList_;
if (head == 0 || (head & kLockBit) != 0) {
return;
}
// Finish the unlock operation: find a thread to wake up.
finishUnlock();
}
} // namespace amd
#endif /*MONITOR_HPP_*/
+3 -1
Féach ar an gComhad
@@ -252,7 +252,9 @@ release(bool, DEBUG_HIP_GRAPH_DOT_PRINT, false, \
release(bool, HIP_ALWAYS_USE_NEW_COMGR_UNBUNDLING_ACTION, false, \
"Force to always use new comgr unbundling action") \
release(bool, DEBUG_HIP_KERNARG_COPY_OPT, true, \
"Enable/Disable multiple kern arg copies") \
"Enable/Disable multiple kern arg copies") \
release(bool, DEBUG_CLR_USE_STDMUTEX_IN_AMD_MONITOR, false, \
"Use std::mutext in amd::monotor") \
namespace amd {