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:
parent
db147fe258
commit
2dbb44e28d
4 changed files with 47 additions and 27 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -276,6 +276,7 @@ class Worker {
|
|||
}
|
||||
|
||||
TimePoint elapsed() const;
|
||||
TimePoint elapsed_time() const;
|
||||
|
||||
LimitsType limits;
|
||||
|
||||
|
|
|
@ -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.
|
||||
startTime = limits.startTime;
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue