From bcb2a89e111a918a48a132a2a29e0c26d724591d Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 10 Feb 2017 07:56:33 +0200 Subject: Redo scheduler task flag as atomic counter Makes for simpler code and also seems to perform better. --- build2/scheduler.cxx | 60 ++++++++++++++++------------------------------------ 1 file changed, 18 insertions(+), 42 deletions(-) (limited to 'build2/scheduler.cxx') diff --git a/build2/scheduler.cxx b/build2/scheduler.cxx index 0e5e280..308310e 100644 --- a/build2/scheduler.cxx +++ b/build2/scheduler.cxx @@ -46,7 +46,7 @@ namespace build2 suspend (size_t start_count, const atomic_count& tc) { wait_slot& s ( - wait_queue_[std::hash () (&tc) % wait_queue_size_]); + wait_queue_[hash () (&tc) % wait_queue_size_]); // This thread is no longer active. // @@ -64,7 +64,7 @@ namespace build2 // if (ready_ != 0) ready_condv_.notify_one (); - else if (task_) + else if (queued_task_count_ != 0) activate_helper (l); } @@ -132,7 +132,7 @@ namespace build2 return; wait_slot& s ( - wait_queue_[std::hash () (&tc) % wait_queue_size_]); + wait_queue_[hash () (&tc) % wait_queue_size_]); // See suspend() for why we must hold the lock. // @@ -185,6 +185,8 @@ namespace build2 if (max_active != 1) task_queues_.reserve (max_threads_); + queued_task_count_.store (0, memory_order_relaxed); + // Pick a nice prime for common max_threads/2 numbers. Experience shows // that we want something close to 2x for small numbers, then reduce to // 1.5x in-between, and 1x for large ones. @@ -237,11 +239,10 @@ namespace build2 stat_max_waiters_ = 0; stat_wait_collisions_ = 0; - task_ = false; - shutdown_ = false; - for (size_t i (0); i != wait_queue_size_; ++i) wait_queue_[i].shutdown = false; + + shutdown_ = false; } void scheduler:: @@ -406,50 +407,25 @@ namespace build2 { s.active_++; - while (s.task_) // There might be a task. + while (s.queued_task_count_ != 0) { - // The tricky part here is to clear the task_ flag with confidence. - // So we quickly scan the queues for any tasks while holding the - // scheduler lock. If all of them are empty, then we can clear the - // task_ flag. + // Queues are never removed and there shouldn't be any reallocations + // since we reserve maximum possible size upfront. Which means we + // can get the current number of queues and release the main lock + // while examining each of them. // - bool empty (true); + size_t n (s.task_queues_.size ()); + l.unlock (); - for (size_t i (0), n (s.task_queues_.size ()); i != n; ++i) + for (size_t i (0); i != n; ++i) { task_queue& tq (*s.task_queues_[i]); - lock ql (tq.mutex); // Lock the queue. - - if (tq.shutdown) - break; - - if (!s.empty_front (tq)) - { - if (empty) - { - empty = false; - l.unlock (); // Release scheduler lock. - } - - // Work on this queue for as long as we can then continue with - // the rest (in which case empty will be false and scheduler - // lock is already released). - // - // Note that the queues are never removed and there shouldn't be - // any reallocations since we reserve the maximum possible size - // upfront. - // - do - s.pop_front (tq, ql); - while (!tq.shutdown && !s.empty_front (tq)); - } + for (lock ql (tq.mutex); !tq.shutdown && !s.empty_front (tq); ) + s.pop_front (tq, ql); } - if (empty) - s.task_ = false; // Scheduler still locked. - else - l.lock (); // Relock and rescan. + l.lock (); } s.active_--; -- cgit v1.1