aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-02-17 16:02:25 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-02-17 16:02:25 +0200
commit68da2afcaa84479142e80e23712793f6ed3e2beb (patch)
treee30f39d361f73ef965a2cfa8c7c4e1e183fc9602
parent25b6505d26f69715f84d773ae838d6ea19a22c19 (diff)
Add support for cheaply starting parallel scheduler pre-tuned to serial
-rw-r--r--libbuild2/module.cxx9
-rw-r--r--libbuild2/scheduler.cxx31
-rw-r--r--libbuild2/scheduler.hxx18
3 files changed, 43 insertions, 15 deletions
diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx
index 3f4f1d0..b7b9bbb 100644
--- a/libbuild2/module.cxx
+++ b/libbuild2/module.cxx
@@ -127,10 +127,11 @@ namespace build2
// Note that we can only do this if we are running serially because
// otherwise we cannot guarantee the scheduler is idle (we could have
// waiting threads from the outer context). This is fine for now since the
- // only two tuning level we use are serial and full concurrency (turns out
- // currently we don't really need this: we will always be called during
- // load or match phases and we always do parallel match; but let's keep it
- // in case things change).
+ // only two tuning level we use are serial and full concurrency. (Turns
+ // out currently we don't really need this: we will always be called
+ // during load or match phases and we always do parallel match; but let's
+ // keep it in case things change. Actually, we may need it, if the
+ // scheduler was started up in a tuned state, like in bpkg).
//
auto sched_tune (ctx.sched.serial ()
? scheduler::tune_guard (ctx.sched, 0)
diff --git a/libbuild2/scheduler.cxx b/libbuild2/scheduler.cxx
index bdd703d..469ded7 100644
--- a/libbuild2/scheduler.cxx
+++ b/libbuild2/scheduler.cxx
@@ -362,8 +362,14 @@ namespace build2
size_t init_active,
size_t max_threads,
size_t queue_depth,
- optional<size_t> max_stack)
+ optional<size_t> max_stack,
+ size_t orig_max_active)
{
+ if (orig_max_active == 0)
+ orig_max_active = max_active;
+ else
+ assert (max_active <= orig_max_active);
+
// Lock the mutex to make sure our changes are visible in (other) active
// threads.
//
@@ -375,16 +381,18 @@ namespace build2
// were asked to run serially.
//
if (max_threads == 0)
- max_threads = (max_active == 1 ? 1 :
- sizeof (void*) < 8 ? 8 : 32) * max_active;
+ max_threads = (orig_max_active == 1
+ ? 1
+ : (sizeof (void*) < 8 ? 8 : 32) * orig_max_active);
assert (shutdown_ &&
init_active != 0 &&
init_active <= max_active &&
- max_active <= max_threads);
+ orig_max_active <= max_threads);
active_ = init_active_ = init_active;
- max_active_ = orig_max_active_ = max_active;
+ max_active_ = max_active;
+ orig_max_active_ = orig_max_active;
max_threads_ = max_threads;
// This value should be proportional to the amount of hardware concurrency
@@ -398,7 +406,7 @@ namespace build2
//
task_queue_depth_ = queue_depth != 0
? queue_depth
- : max_active * 8;
+ : orig_max_active_ * 8;
queued_task_count_.store (0, memory_order_relaxed);
@@ -421,6 +429,8 @@ namespace build2
shutdown_ = false;
+ // Delay thread startup if serial.
+ //
if (max_active_ != 1)
dead_thread_ = thread (deadlock_monitor, this);
}
@@ -429,7 +439,7 @@ namespace build2
tune (size_t max_active)
{
// Note that if we tune a parallel scheduler to run serially, we will
- // still have the deadlock monitoring thread running.
+ // still have the deadlock monitoring thread loitering around.
// With multiple initial active threads we will need to make changes to
// max_active_ visible to other threads and which we currently say can be
@@ -451,6 +461,11 @@ namespace build2
lock l (wait_idle ());
swap (max_active_, max_active);
+
+ // Start the deadlock thread if its startup was delayed.
+ //
+ if (max_active_ != 1 && !dead_thread_.joinable ())
+ dead_thread_ = thread (deadlock_monitor, this);
}
return max_active == orig_max_active_ ? 0 : max_active;
@@ -519,7 +534,7 @@ namespace build2
// Wait for the deadlock monitor (the only remaining thread).
//
- if (orig_max_active_ != 1) // See tune() for why not max_active_.
+ if (dead_thread_.joinable ())
{
l.unlock ();
dead_condv_.notify_one ();
diff --git a/libbuild2/scheduler.hxx b/libbuild2/scheduler.hxx
index dcde79b..76b3263 100644
--- a/libbuild2/scheduler.hxx
+++ b/libbuild2/scheduler.hxx
@@ -301,14 +301,25 @@ namespace build2
// If the maximum threads or task queue depth arguments are unspecified,
// then appropriate defaults are used.
//
+ // Passing non-zero orig_max_active (normally the real max active) allows
+ // starting up a pre-tuned scheduler. In particular, starting a pre-tuned
+ // to serial scheduler is relatively cheap since starting the deadlock
+ // detection thread is delayed until the scheduler is re-tuned.
+ //
explicit
scheduler (size_t max_active,
size_t init_active = 1,
size_t max_threads = 0,
size_t queue_depth = 0,
- optional<size_t> max_stack = nullopt)
+ optional<size_t> max_stack = nullopt,
+ size_t orig_max_active = 0)
{
- startup (max_active, init_active, max_threads, queue_depth, max_stack);
+ startup (max_active,
+ init_active,
+ max_threads,
+ queue_depth,
+ max_stack,
+ orig_max_active);
}
// Start the scheduler.
@@ -318,7 +329,8 @@ namespace build2
size_t init_active = 1,
size_t max_threads = 0,
size_t queue_depth = 0,
- optional<size_t> max_stack = nullopt);
+ optional<size_t> max_stack = nullopt,
+ size_t orig_max_active = 0);
// Return true if the scheduler was started up.
//