1
0
Fork 0
mirror of https://github.com/sockspls/badfish synced 2025-04-29 16:23:09 +00:00

Fix nodestime

1. The current time management system utilizes limits.inc and
limits.time, which can represent either milliseconds or node count,
depending on whether the nodestime option is active. There have been
several modifications which brought Elo gain for typical uses (i.e.
real-time matches), however some of these changes overlooked such
distinction. This patch adjusts constants and multiplication/division to
more accurately simulate real TC conditions when nodestime is used.

2. The advance_nodes_time function has a bug that can extend the time
limit when availableNodes reaches exact zero. This patch fixes the bug
by initializing the variable to -1 and make sure it does not go below
zero.

3. elapsed_time function is newly introduced to print PV in the UCI
output based on real time. This makes PV output more consistent with the
behavior of trivial use cases.

closes https://github.com/official-stockfish/Stockfish/pull/5186

No functional changes
This commit is contained in:
MinetaS 2024-05-08 03:26:09 +09:00 committed by Joost VandeVondele
parent db147fe258
commit 2dbb44e28d
4 changed files with 47 additions and 27 deletions

View file

@ -190,8 +190,8 @@ void Search::Worker::start_searching() {
// When playing in 'nodes as time' mode, subtract the searched nodes from
// the available ones before exiting.
if (limits.npmsec)
main_manager()->tm.advance_nodes_time(limits.inc[rootPos.side_to_move()]
- threads.nodes_searched());
main_manager()->tm.advance_nodes_time(threads.nodes_searched()
- limits.inc[rootPos.side_to_move()]);
Worker* bestThread = this;
Skill skill =
@ -347,7 +347,7 @@ void Search::Worker::iterative_deepening() {
// When failing high/low give some update (without cluttering
// the UI) before a re-search.
if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta)
&& elapsed() > 3000)
&& elapsed_time() > 3000)
main_manager()->pv(*this, threads, tt, rootDepth);
// In case of failing low/high increase aspiration window and
@ -378,7 +378,7 @@ void Search::Worker::iterative_deepening() {
std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1);
if (mainThread
&& (threads.stop || pvIdx + 1 == multiPV || elapsed() > 3000)
&& (threads.stop || pvIdx + 1 == multiPV || elapsed_time() > 3000)
// A thread that aborted search can have mated-in/TB-loss PV and score
// that cannot be trusted, i.e. it can be delayed or refuted if we would have
// had time to fully search other root-moves. Thus we suppress this output and
@ -935,7 +935,7 @@ moves_loop: // When in check, search starts here
ss->moveCount = ++moveCount;
if (rootNode && is_mainthread() && elapsed() > 3000)
if (rootNode && is_mainthread() && elapsed_time() > 3000)
{
main_manager()->updates.onIter(
{depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx});
@ -1647,10 +1647,20 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) {
return (reductionScale + 1318 - delta * 760 / rootDelta) / 1024 + (!i && reductionScale > 1066);
}
// elapsed() returns the time elapsed since the search started. If the
// 'nodestime' option is enabled, it will return the count of nodes searched
// instead. This function is called to check whether the search should be
// stopped based on predefined thresholds like time limits or nodes searched.
//
// elapsed_time() returns the actual time elapsed since the start of the search.
// This function is intended for use only when printing PV outputs, and not used
// for making decisions within the search algorithm itself.
TimePoint Search::Worker::elapsed() const {
return main_manager()->tm.elapsed([this]() { return threads.nodes_searched(); });
}
TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); }
namespace {
// Adjusts a mate or TB score from "plies to mate from the root"
@ -1900,7 +1910,7 @@ void SearchManager::pv(const Search::Worker& worker,
const auto& rootMoves = worker.rootMoves;
const auto& pos = worker.rootPos;
size_t pvIdx = worker.pvIdx;
TimePoint time = tm.elapsed([nodes]() { return nodes; }) + 1;
TimePoint time = tm.elapsed_time() + 1;
size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size());
uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0);

View file

@ -276,6 +276,7 @@ class Worker {
}
TimePoint elapsed() const;
TimePoint elapsed_time() const;
LimitsType limits;

View file

@ -32,12 +32,12 @@ TimePoint TimeManagement::optimum() const { return optimumTime; }
TimePoint TimeManagement::maximum() const { return maximumTime; }
void TimeManagement::clear() {
availableNodes = 0; // When in 'nodes as time' mode
availableNodes = -1; // When in 'nodes as time' mode
}
void TimeManagement::advance_nodes_time(std::int64_t nodes) {
assert(useNodesTime);
availableNodes += nodes;
availableNodes = std::max(int64_t(0), availableNodes - nodes);
}
// Called at the beginning of the search and calculates
@ -48,14 +48,17 @@ void TimeManagement::init(Search::LimitsType& limits,
Color us,
int ply,
const OptionsMap& options) {
// If we have no time, no need to initialize TM, except for the start time,
// which is used by movetime.
TimePoint npmsec = TimePoint(options["nodestime"]);
// If we have no time, we don't need to fully initialize TM.
// startTime is used by movetime and useNodesTime is used in elapsed calls.
startTime = limits.startTime;
useNodesTime = npmsec != 0;
if (limits.time[us] == 0)
return;
TimePoint moveOverhead = TimePoint(options["Move Overhead"]);
TimePoint npmsec = TimePoint(options["nodestime"]);
// optScale is a percentage of available time to use for the current move.
// maxScale is a multiplier applied to optimumTime.
@ -65,26 +68,31 @@ void TimeManagement::init(Search::LimitsType& limits,
// to nodes, and use resulting values in time management formulas.
// WARNING: to avoid time losses, the given npmsec (nodes per millisecond)
// must be much lower than the real engine speed.
if (npmsec)
if (useNodesTime)
{
useNodesTime = true;
if (!availableNodes) // Only once at game start
if (availableNodes == -1) // Only once at game start
availableNodes = npmsec * limits.time[us]; // Time is in msec
// Convert from milliseconds to nodes
limits.time[us] = TimePoint(availableNodes);
limits.inc[us] *= npmsec;
limits.npmsec = npmsec;
moveOverhead *= npmsec;
}
// These numbers are used where multiplications, divisions or comparisons
// with constants are involved.
const int64_t scaleFactor = useNodesTime ? npmsec : 1;
const TimePoint scaledTime = limits.time[us] / scaleFactor;
const TimePoint scaledInc = limits.inc[us] / scaleFactor;
// Maximum move horizon of 50 moves
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
// if less than one second, gradually reduce mtg
if (limits.time[us] < 1000 && (double(mtg) / limits.time[us] > 0.05))
// If less than one second, gradually reduce mtg
if (scaledTime < 1000 && double(mtg) / scaledInc > 0.05)
{
mtg = limits.time[us] * 0.05;
mtg = scaledTime * 0.05;
}
// Make sure timeLeft is > 0 since we may use it as a divisor
@ -97,15 +105,15 @@ void TimeManagement::init(Search::LimitsType& limits,
if (limits.movestogo == 0)
{
// Use extra time with larger increments
double optExtra = limits.inc[us] < 500 ? 1.0 : 1.13;
double optExtra = scaledInc < 500 ? 1.0 : 1.13;
// Calculate time constants based on current time left.
double optConstant =
std::min(0.00308 + 0.000319 * std::log10(limits.time[us] / 1000.0), 0.00506);
double maxConstant = std::max(3.39 + 3.01 * std::log10(limits.time[us] / 1000.0), 2.93);
double logTimeInSec = std::log10(scaledTime / 1000.0);
double optConstant = std::min(0.00308 + 0.000319 * logTimeInSec, 0.00506);
double maxConstant = std::max(3.39 + 3.01 * logTimeInSec, 2.93);
optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant,
0.213 * limits.time[us] / double(timeLeft))
0.213 * limits.time[us] / timeLeft)
* optExtra;
maxScale = std::min(6.64, maxConstant + ply / 12.0);
}
@ -113,7 +121,7 @@ void TimeManagement::init(Search::LimitsType& limits,
// x moves in y seconds (+ z increment)
else
{
optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft));
optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / timeLeft);
maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
}

View file

@ -42,8 +42,9 @@ class TimeManagement {
TimePoint maximum() const;
template<typename FUNC>
TimePoint elapsed(FUNC nodes) const {
return useNodesTime ? TimePoint(nodes()) : now() - startTime;
return useNodesTime ? TimePoint(nodes()) : elapsed_time();
}
TimePoint elapsed_time() const { return now() - startTime; };
void clear();
void advance_nodes_time(std::int64_t nodes);
@ -53,7 +54,7 @@ class TimeManagement {
TimePoint optimumTime;
TimePoint maximumTime;
std::int64_t availableNodes = 0; // When in 'nodes as time' mode
std::int64_t availableNodes = -1; // When in 'nodes as time' mode
bool useNodesTime = false; // True if we are in 'nodes as time' mode
};