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.
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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();
|
||||
|
@@ -16,6 +16,7 @@
|
||||
#include "mcf.h"
|
||||
#include "flowmapper.h"
|
||||
#include "../command_func.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
@@ -27,29 +28,76 @@
|
||||
/* 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));
|
||||
}
|
||||
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()) {
|
||||
LinkGraphJob *job = new LinkGraphJob(*next);
|
||||
job->SpawnThread();
|
||||
this->running.push_back(job);
|
||||
uint duration_multiplier = CeilDiv(scaling * cost, total_cost);
|
||||
std::unique_ptr<LinkGraphJob> 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<LinkGraphJob> &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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the next finished job, if available.
|
||||
*/
|
||||
@@ -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<LinkGraphJob> 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.
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#define LINKGRAPHSCHEDULE_H
|
||||
|
||||
#include "linkgraph.h"
|
||||
#include <memory>
|
||||
|
||||
class LinkGraphJob;
|
||||
|
||||
@@ -40,7 +41,7 @@ private:
|
||||
LinkGraphSchedule();
|
||||
~LinkGraphSchedule();
|
||||
typedef std::list<LinkGraph *> GraphList;
|
||||
typedef std::list<LinkGraphJob *> JobList;
|
||||
typedef std::list<std::unique_ptr<LinkGraphJob>> JobList;
|
||||
friend const SaveLoad *GetLinkGraphScheduleDesc();
|
||||
|
||||
protected:
|
||||
|
Reference in New Issue
Block a user