From 84e61b690acca0a6cbac08bcce928d4aa514409b Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 31 Oct 2016 00:21:01 +0000 Subject: [PATCH] Linkgraph: Changes to job scheduling algorithm. This is to improve responsiveness of link graph updates, whilst avoiding being blocked waiting for updates to complete. Previously, large numbers of cheap jobs resulted in poor responsiveness as it took a long time for jobs to cycle round. Add 'linkgraph' debug category. --- src/debug.cpp | 2 + src/debug.h | 1 + src/linkgraph/linkgraph.h | 6 +++ src/linkgraph/linkgraphjob.cpp | 8 +-- src/linkgraph/linkgraphjob.h | 2 +- src/linkgraph/linkgraphschedule.cpp | 84 ++++++++++++++++++++++------- src/linkgraph/linkgraphschedule.h | 3 +- 7 files changed, 82 insertions(+), 24 deletions(-) diff --git a/src/debug.cpp b/src/debug.cpp index 016496eb86..97611c1bfe 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -46,6 +46,7 @@ int _debug_gamelog_level; int _debug_desync_level; int _debug_yapfdesync_level; int _debug_console_level; +int _debug_linkgraph_level; #ifdef RANDOM_DEBUG int _debug_random_level; #endif @@ -75,6 +76,7 @@ struct DebugLevel { DEBUG_LEVEL(desync), DEBUG_LEVEL(yapfdesync), DEBUG_LEVEL(console), + DEBUG_LEVEL(linkgraph), #ifdef RANDOM_DEBUG DEBUG_LEVEL(random), #endif diff --git a/src/debug.h b/src/debug.h index 590bc60204..47e90cd03f 100644 --- a/src/debug.h +++ b/src/debug.h @@ -54,6 +54,7 @@ extern int _debug_desync_level; extern int _debug_yapfdesync_level; extern int _debug_console_level; + extern int _debug_linkgraph_level; #ifdef RANDOM_DEBUG extern int _debug_random_level; #endif diff --git a/src/linkgraph/linkgraph.h b/src/linkgraph/linkgraph.h index 799f22c780..87b676f83c 100644 --- a/src/linkgraph/linkgraph.h +++ b/src/linkgraph/linkgraph.h @@ -15,6 +15,7 @@ #include "../core/pool_type.hpp" #include "../core/smallmap_type.hpp" #include "../core/smallmatrix_type.hpp" +#include "../core/bitmath_func.hpp" #include "../station_base.h" #include "../cargotype.h" #include "../date_func.h" @@ -523,6 +524,11 @@ public: NodeID AddNode(const Station *st); void RemoveNode(NodeID id); + inline uint CalculateCostEstimate() const { + uint64_t size_squared = this->Size() * this->Size(); + return size_squared * FindLastBit(size_squared * size_squared); // N^2 * 4log_2(N) + } + protected: friend class LinkGraph::ConstNode; friend class LinkGraph::Node; diff --git a/src/linkgraph/linkgraphjob.cpp b/src/linkgraph/linkgraphjob.cpp index 62a7aa3758..0b60edfbb6 100644 --- a/src/linkgraph/linkgraphjob.cpp +++ b/src/linkgraph/linkgraphjob.cpp @@ -28,9 +28,9 @@ INSTANTIATE_POOL_METHODS(LinkGraphJob) */ /* static */ Path *Path::invalid_path = new Path(INVALID_NODE, true); -static DateTicks GetLinkGraphJobJoinDateTicks() +static DateTicks GetLinkGraphJobJoinDateTicks(uint duration_multiplier) { - DateTicks ticks = _settings_game.linkgraph.recalc_time * DAY_TICKS; + DateTicks ticks = _settings_game.linkgraph.recalc_time * DAY_TICKS * duration_multiplier; if (_settings_game.linkgraph.recalc_not_scaled_by_daylength) { ticks /= _settings_game.economy.day_length_factor; } @@ -43,13 +43,13 @@ static DateTicks GetLinkGraphJobJoinDateTicks() * original. The job is immediately started. * @param orig Original LinkGraph to be copied. */ -LinkGraphJob::LinkGraphJob(const LinkGraph &orig) : +LinkGraphJob::LinkGraphJob(const LinkGraph &orig, uint duration_multiplier) : /* Copying the link graph here also copies its index member. * This is on purpose. */ link_graph(orig), settings(_settings_game.linkgraph), thread(NULL), - join_date_ticks(GetLinkGraphJobJoinDateTicks()), + join_date_ticks(GetLinkGraphJobJoinDateTicks(duration_multiplier)), start_date_ticks((_date * DAY_TICKS) + _date_fract), job_completed(false) { diff --git a/src/linkgraph/linkgraphjob.h b/src/linkgraph/linkgraphjob.h index b4f53e1884..8b36d21c01 100644 --- a/src/linkgraph/linkgraphjob.h +++ b/src/linkgraph/linkgraphjob.h @@ -272,7 +272,7 @@ public: LinkGraphJob() : settings(_settings_game.linkgraph), thread(NULL), join_date_ticks(INVALID_DATE), start_date_ticks(INVALID_DATE), job_completed(false) {} - LinkGraphJob(const LinkGraph &orig); + LinkGraphJob(const LinkGraph &orig, uint duration_multiplier); ~LinkGraphJob(); void Init(); diff --git a/src/linkgraph/linkgraphschedule.cpp b/src/linkgraph/linkgraphschedule.cpp index 4d80629005..939c9f3422 100644 --- a/src/linkgraph/linkgraphschedule.cpp +++ b/src/linkgraph/linkgraphschedule.cpp @@ -16,6 +16,7 @@ #include "mcf.h" #include "flowmapper.h" #include "../command_func.h" +#include #include "../safeguards.h" @@ -27,27 +28,74 @@ /* static */ LinkGraphSchedule LinkGraphSchedule::instance; /** - * Start the next job in the schedule. + * Start the next job(s) in the schedule. + * + * The cost estimate of a link graph job is C ~ N^2 log N, where + * N is the number of nodes in the job link graph. + * The cost estimate is summed for all running and scheduled jobs to form the total cost estimate T = sum C. + * The nominal cycle time (in recalc intervals) required to schedule all jobs is calculated as S = log_2 T. + * Hence the nominal duration of an individual job (in recalc intervals) is D = ceil(S * C / T) + * The cost budget for an individual call to this method is given by T / S. + * + * The purpose of this algorithm is so that overall responsiveness is not hindered by large numbers of small/cheap + * jobs which would previously need to be cycled through individually, but equally large/slow jobs have an extended + * duration in which to execute, to avoid unnecessary pauses. */ void LinkGraphSchedule::SpawnNext() { if (this->schedule.empty()) return; - LinkGraph *next = this->schedule.front(); - LinkGraph *first = next; - while (next->Size() < 2) { - this->schedule.splice(this->schedule.end(), this->schedule, this->schedule.begin()); - next = this->schedule.front(); - if (next == first) return; + + GraphList schedule_to_back; + uint total_cost = 0; + for (auto iter = this->schedule.begin(); iter != this->schedule.end();) { + auto current = iter; + ++iter; + const LinkGraph *lg = *current; + + if (lg->Size() < 2) { + schedule_to_back.splice(schedule_to_back.end(), this->schedule, current); + } else { + total_cost += lg->CalculateCostEstimate(); + } } - assert(next == LinkGraph::Get(next->index)); - this->schedule.pop_front(); - if (LinkGraphJob::CanAllocateItem()) { - LinkGraphJob *job = new LinkGraphJob(*next); - job->SpawnThread(); - this->running.push_back(job); - } else { - NOT_REACHED(); + for (auto &it : this->running) { + total_cost += it->Graph().CalculateCostEstimate(); } + uint scaling = FindLastBit(total_cost); + uint cost_budget = total_cost / scaling; + uint used_budget = 0; + while (used_budget < cost_budget && !this->schedule.empty()) { + LinkGraph *lg = this->schedule.front(); + assert(lg == LinkGraph::Get(lg->index)); + this->schedule.pop_front(); + uint cost = lg->CalculateCostEstimate(); + used_budget += cost; + if (LinkGraphJob::CanAllocateItem()) { + uint duration_multiplier = CeilDiv(scaling * cost, total_cost); + std::unique_ptr job(new LinkGraphJob(*lg, duration_multiplier)); + job->SpawnThread(); // todo + if (this->running.empty() || job->JoinDateTicks() >= this->running.back()->JoinDateTicks()) { + this->running.push_back(std::move(job)); + DEBUG(linkgraph, 3, "LinkGraphSchedule::SpawnNext(): Running job: id: %u, nodes: %u, cost: %u, duration_multiplier: %u", + lg->index, lg->Size(), cost, duration_multiplier); + } else { + // find right place to insert + auto iter = std::upper_bound(this->running.begin(), this->running.end(), job->JoinDateTicks(), [](DateTicks a, const std::unique_ptr &b) { + return a < b->JoinDateTicks(); + }); + this->running.insert(iter, std::move(job)); + DEBUG(linkgraph, 3, "LinkGraphSchedule::SpawnNext(): Running job (re-ordering): id: %u, nodes: %u, cost: %u, duration_multiplier: %u", + lg->index, lg->Size(), cost, duration_multiplier); + } + } else { + NOT_REACHED(); + } + } + + this->schedule.splice(this->schedule.end(), schedule_to_back); + + DEBUG(linkgraph, 2, "LinkGraphSchedule::SpawnNext(): Linkgraph job totals: cost: %u, budget: %u, scaling: %u, scheduled: %zu, running: %zu", + total_cost, cost_budget, scaling, this->schedule.size(), this->running.size()); } /** @@ -74,11 +122,11 @@ bool LinkGraphSchedule::IsJoinWithUnfinishedJobDue() const void LinkGraphSchedule::JoinNext() { while (!(this->running.empty())) { - LinkGraphJob *next = this->running.front(); - if (!next->IsFinished()) return; + if (!this->running.front()->IsFinished()) return; + std::unique_ptr next = std::move(this->running.front()); this->running.pop_front(); LinkGraphID id = next->LinkGraphIndex(); - delete next; // implicitly joins the thread + next.reset(); // implicitly joins the thread if (LinkGraph::IsValidID(id)) { LinkGraph *lg = LinkGraph::Get(id); this->Unqueue(lg); // Unqueue to avoid double-queueing recycled IDs. diff --git a/src/linkgraph/linkgraphschedule.h b/src/linkgraph/linkgraphschedule.h index ab69239586..51369a8de6 100644 --- a/src/linkgraph/linkgraphschedule.h +++ b/src/linkgraph/linkgraphschedule.h @@ -13,6 +13,7 @@ #define LINKGRAPHSCHEDULE_H #include "linkgraph.h" +#include class LinkGraphJob; @@ -40,7 +41,7 @@ private: LinkGraphSchedule(); ~LinkGraphSchedule(); typedef std::list GraphList; - typedef std::list JobList; + typedef std::list> JobList; friend const SaveLoad *GetLinkGraphScheduleDesc(); protected: