Skip to content

Commit

Permalink
Switch time management to 64 bits
Browse files Browse the repository at this point in the history
This is a patch to fix issue #1498, switching the time management variables
to 64 bits to avoid overflow of time variables after 25 days.

There was a bug in Stockfish 9 causing the output to be wrong after
2^31 milliseconds search. Here is a long run from the starting position:

info depth 64 seldepth 87 multipv 1 score cp 23 nodes 13928920239402
nps 0 tbhits 0 time -504995523 pv g1f3 d7d5 d2d4 g8f6 c2c4 d5c4 e2e3 e7e6 f1c4
c7c5 e1g1 b8c6 d4c5 d8d1 f1d1 f8c5 c4e2 e8g8 a2a3 c5e7 b2b4 f8d8 b1d2 b7b6 c1b2
c8b7 a1c1 a8c8 c1c2 c6e5 d1c1 c8c2 c1c2 e5f3 d2f3 a7a5 b4b5 e7c5 f3d4 d8c8 d4b3
c5d6 c2c8 b7c8 b3d2 c8b7 d2c4 d6c5 e2f3 b7d5 f3d5 e6d5 c4e5 a5a4 e5d3 f6e4 d3c5
e4c5 b2d4 c5e4 d4b6 e4d6 g2g4 d6b5 b6c5 b5c7 g1g2 c7e6 c5d6 g7g6

We check at compile time that the TimePoint type is exactly 64 bits long for
the compiler (TimePoint is our alias in Stockfish for std::chrono::milliseconds
-- it is a signed integer type of at least 45 bits according to the C++ standard,
but will most probably be implemented as a 64 bits signed integer on modern
compilers), and we use this TimePoint type consistently across the code.

Bug report by user "fischerandom" on the TCEC chat (thanks), and the
patch includes code and suggestions by user "WOnder93" and Ronald de Man.

Fixes issue:          #1498
Closes pull request:  #1510

No functional change.
  • Loading branch information
snicolet committed Mar 27, 2018
1 parent f0f6da2 commit a03e98d
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/bitboard.h
Expand Up @@ -166,7 +166,7 @@ template<Direction D>
constexpr Bitboard shift(Bitboard b) {
return D == NORTH ? b << 8 : D == SOUTH ? b >> 8
: D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1
: D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7
: D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7
: D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9
: 0;
}
Expand Down
2 changes: 2 additions & 0 deletions src/misc.h
Expand Up @@ -41,6 +41,8 @@ void dbg_print();

typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds

static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");

inline TimePoint now() {
return std::chrono::duration_cast<std::chrono::milliseconds>
(std::chrono::steady_clock::now().time_since_epoch()).count();
Expand Down
4 changes: 2 additions & 2 deletions src/search.cpp
Expand Up @@ -1495,7 +1495,7 @@ void MainThread::check_time() {

static TimePoint lastInfoTime = now();

int elapsed = Time.elapsed();
TimePoint elapsed = Time.elapsed();
TimePoint tick = Limits.startTime + elapsed;

if (tick - lastInfoTime >= 1000)
Expand All @@ -1521,7 +1521,7 @@ void MainThread::check_time() {
string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {

std::stringstream ss;
int elapsed = Time.elapsed() + 1;
TimePoint elapsed = Time.elapsed() + 1;
const RootMoves& rootMoves = pos.this_thread()->rootMoves;
size_t PVIdx = pos.this_thread()->PVIdx;
size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size());
Expand Down
5 changes: 2 additions & 3 deletions src/search.h
Expand Up @@ -90,10 +90,9 @@ struct LimitsType {
}

std::vector<Move> searchmoves;
int time[COLOR_NB], inc[COLOR_NB], npmsec, movestogo, depth,
movetime, mate, perft, infinite;
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
int movestogo, depth, mate, perft, infinite;
int64_t nodes;
TimePoint startTime;
};

extern LimitsType Limits;
Expand Down
43 changes: 22 additions & 21 deletions src/timeman.cpp
Expand Up @@ -52,21 +52,21 @@ namespace {
}

template<TimeType T>
int remaining(int myTime, int movesToGo, int ply, int slowMover) {
TimePoint remaining(TimePoint myTime, int movesToGo, int ply, TimePoint slowMover) {

constexpr double TMaxRatio = (T == OptimumTime ? 1 : MaxRatio);
constexpr double TStealRatio = (T == OptimumTime ? 0 : StealRatio);
constexpr double TMaxRatio = (T == OptimumTime ? 1.0 : MaxRatio);
constexpr double TStealRatio = (T == OptimumTime ? 0.0 : StealRatio);

double moveImportance = (move_importance(ply) * slowMover) / 100;
double otherMovesImportance = 0;
double moveImportance = (move_importance(ply) * slowMover) / 100.0;
double otherMovesImportance = 0.0;

for (int i = 1; i < movesToGo; ++i)
otherMovesImportance += move_importance(ply + 2 * i);

double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance);
double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance);

return int(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast
return TimePoint(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast
}

} // namespace
Expand All @@ -83,22 +83,23 @@ namespace {

void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {

int minThinkingTime = Options["Minimum Thinking Time"];
int moveOverhead = Options["Move Overhead"];
int slowMover = Options["Slow Mover"];
int npmsec = Options["nodestime"];
TimePoint minThinkingTime = Options["Minimum Thinking Time"];
TimePoint moveOverhead = Options["Move Overhead"];
TimePoint slowMover = Options["Slow Mover"];
TimePoint npmsec = Options["nodestime"];
TimePoint hypMyTime;

// If we have to play in 'nodes as time' mode, then convert from time
// to nodes, and use resulting values in time management formulas.
// WARNING: Given npms (nodes per millisecond) must be much lower then
// the real engine speed to avoid time losses.
// WARNING: to avoid time losses, the given npmsec (nodes per millisecond)
// must be much lower than the real engine speed.
if (npmsec)
{
if (!availableNodes) // Only once at game start
availableNodes = npmsec * limits.time[us]; // Time is in msec

// Convert from millisecs to nodes
limits.time[us] = (int)availableNodes;
// Convert from milliseconds to nodes
limits.time[us] = TimePoint(availableNodes);
limits.inc[us] *= npmsec;
limits.npmsec = npmsec;
}
Expand All @@ -108,20 +109,20 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {

const int maxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon;

// We calculate optimum time usage for different hypothetical "moves to go"-values
// We calculate optimum time usage for different hypothetical "moves to go" values
// and choose the minimum of calculated search time values. Usually the greatest
// hypMTG gives the minimum values.
for (int hypMTG = 1; hypMTG <= maxMTG; ++hypMTG)
{
// Calculate thinking time for hypothetical "moves to go"-value
int hypMyTime = limits.time[us]
+ limits.inc[us] * (hypMTG - 1)
- moveOverhead * (2 + std::min(hypMTG, 40));
hypMyTime = limits.time[us]
+ limits.inc[us] * (hypMTG - 1)
- moveOverhead * (2 + std::min(hypMTG, 40));

hypMyTime = std::max(hypMyTime, 0);
hypMyTime = std::max(hypMyTime, TimePoint(0));

int t1 = minThinkingTime + remaining<OptimumTime>(hypMyTime, hypMTG, ply, slowMover);
int t2 = minThinkingTime + remaining<MaxTime >(hypMyTime, hypMTG, ply, slowMover);
TimePoint t1 = minThinkingTime + remaining<OptimumTime>(hypMyTime, hypMTG, ply, slowMover);
TimePoint t2 = minThinkingTime + remaining<MaxTime >(hypMyTime, hypMTG, ply, slowMover);

optimumTime = std::min(t1, optimumTime);
maximumTime = std::min(t2, maximumTime);
Expand Down
10 changes: 5 additions & 5 deletions src/timeman.h
Expand Up @@ -31,16 +31,16 @@
class TimeManagement {
public:
void init(Search::LimitsType& limits, Color us, int ply);
int optimum() const { return optimumTime; }
int maximum() const { return maximumTime; }
int elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); }
TimePoint optimum() const { return optimumTime; }
TimePoint maximum() const { return maximumTime; }
TimePoint elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); }

int64_t availableNodes; // When in 'nodes as time' mode

private:
TimePoint startTime;
int optimumTime;
int maximumTime;
TimePoint optimumTime;
TimePoint maximumTime;
};

extern TimeManagement Time;
Expand Down

3 comments on commit a03e98d

@DU-jdto
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the cast to int on line 36 of timeman.h now incorrect?

@snicolet
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DU-jdto Yes indeed, thanks!

@snicolet
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected in 9953bff

Please sign in to comment.