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: