Skip to content
Snippets Groups Projects
Commit 2cba082b authored by Florian Fischer's avatar Florian Fischer
Browse files

Make decision how many workers to notify in Runtime

The decision how many workers should be notified on new work should be
made in the runtime.
This is the whole reason why WorkerWakeupStrategies provide notify{One,Many,All} functions.

This change also simplifies the logic used in SemaphoreWorkerSleepStrategy.
The old simply copy-pasted semaphore based implementation could then
be split up into much smaller and simpler pieces.
SemaphoreWorkerSleepStrategy::notify{All, Many} is could actually be faster
because it can now use the semaphores notify_many function instead of
calling notifyInternal multiple times.
parent 3ab70f90
No related branches found
No related tags found
1 merge request!183Make decision how many workers to notify in Runtime
......@@ -17,6 +17,7 @@
#include "CallerEnvironment.hpp"
#include "Context.hpp"
#include "Debug.hpp"
#include "Emper.hpp"
#include "NextFiberResult.hpp"
#include "Scheduler.hpp" // for Scheduler
#include "Worker.hpp"
......@@ -90,7 +91,13 @@ class Runtime : public Logger<LogSubsystem::RUNTI> {
template <CallerEnvironment callerEnvironment = CallerEnvironment::EMPER>
inline void wakeupSleepingWorkers() {
workerSleepStrategy.notifyOne<callerEnvironment>();
if constexpr (::emper::WORKER_WAKEUP_STRATEGY == ::emper::WorkerWakeupStrategy::one) {
workerSleepStrategy.notifyOne<callerEnvironment>();
} else if constexpr (::emper::WORKER_WAKEUP_STRATEGY == ::emper::WorkerWakeupStrategy::all) {
workerSleepStrategy.notifyAll<callerEnvironment>();
} else {
ABORT("Unknown CallerEnvironment");
}
}
void maybeTerminateWorker() {
......
......@@ -5,6 +5,7 @@
#include <stdexcept>
#include "CallerEnvironment.hpp"
#include "Emper.hpp"
namespace emper::sleep_strategy {
......
......@@ -5,8 +5,6 @@
#include <atomic>
#include "CallerEnvironment.hpp"
#include "Debug.hpp"
#include "Emper.hpp"
#include "Worker.hpp"
#include "emper-common.h"
#include "emper-config.h"
......@@ -102,15 +100,15 @@ class AbstractSemaphoreWorkerSleepStrategy
}
}
// The actual semaphore based worker sleep algorithm
template <CallerEnvironment callerEnvironment>
void notifyInternal() {
[[nodiscard]] inline auto mustNotify() -> bool {
typename Sem::CounterType skipWakeupThreshold;
if constexpr (callerEnvironment == CallerEnvironment::ANYWHERE) {
// On external work we always increment the semaphore unless we observe
// that its value is > workerCount.
// If we observe semValue > workerCount we are ensured that some worker will iterate
// its dispatchLoop again and must observe the new work.
// TODO: Could it be >= workerCount ?
skipWakeupThreshold = workerCount;
} else {
// For work from within emper we skip wakeup if we observe no one sleeping.
......@@ -121,40 +119,15 @@ class AbstractSemaphoreWorkerSleepStrategy
// Note that sem_getvalue() is allowed to return 0 if there are
// waiting workers, hence we need to set the threshold also to
// 0. This has the disadvantage that we will perform one
// unnecessary sem_post. If we ever switch the wakeupSem
// implementation, then the skipWakeupThreshold value should be
// unnecessary sem_post. If we are sure the wakeupSem
// implementation does not return 0 with waiters,
// then the skipWakeupThreshold value should be
// reviewed and potentially changed to '-1'.
skipWakeupThreshold = 0;
}
auto semValue = wakeupSem.getValue();
if (semValue > skipWakeupThreshold) {
return;
}
if constexpr (::emper::WORKER_WAKEUP_STRATEGY == ::emper::WorkerWakeupStrategy::one) {
wakeupSem.notify();
} else if constexpr (::emper::WORKER_WAKEUP_STRATEGY == ::emper::WorkerWakeupStrategy::all) {
// notify all we observed sleeping
// It is sound to increment the semaphore to much, thus this will only cause
// workers to iterate the dispatchLoop more often before actually sleeping
// TODO: Switch to c++20 std::counting_semaphore, which has release(std::ptrdiff_t)
// Reading the manpage explains the function.
// POSIX sem_getvalue is allowed to return 0 or a negative count if there are
// waiters.
// Linux sem_getvalue indeed does return 0
// To notify all sleeping workers we increment the semaphore once for each worker.
if (semValue == 0) {
semValue = workerCount;
}
// make sure that the amount to notify is always positive
wakeupSem.notify_many(semValue < 0 ? -semValue : semValue);
} else {
ABORT("Unknown CallerEnvironment");
}
return !(semValue > skipWakeupThreshold);
}
public:
......@@ -174,19 +147,26 @@ class AbstractSemaphoreWorkerSleepStrategy
template <CallerEnvironment callerEnvironment>
inline void notifyOne() {
notifyInternal<callerEnvironment>();
if (mustNotify<callerEnvironment>()) {
wakeupSem.notify();
}
}
template <CallerEnvironment callerEnvironment>
inline void notifyMany(unsigned count) {
for (unsigned i = 0; i < count; ++i) {
notifyInternal<callerEnvironment>();
if (mustNotify<callerEnvironment>()) {
wakeupSem.notify_many(count);
}
}
// notify all workers
// It is sound to increment the semaphore to much, thus this will only cause
// workers to iterate the dispatchLoop more often before actually sleeping
template <CallerEnvironment callerEnvironment>
inline void notifyAll() {
notifyMany<callerEnvironment>(workerCount);
if (mustNotify<callerEnvironment>()) {
wakeupSem.notify_many(workerCount);
}
}
template <CallerEnvironment callerEnvironment>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment