StateInfo is usually allocated on the stack by search()
authorMarco Costalba <mcostalba@gmail.com>
Mon, 11 Apr 2016 14:45:36 +0000 (16:45 +0200)
committerMarco Costalba <mcostalba@gmail.com>
Sun, 17 Apr 2016 06:29:33 +0000 (08:29 +0200)
And passed in do_move(), this ensures maximum efficiency and
speed and at the same time unlimited move numbers.

The draw back is that to handle Position init we need to
reserve a StateInfo inside Position itself and use at
init time and when copying from another Position.

After lazy SMP we don't need anymore this gimmick and we can
get rid of this special case and always pass an external
StateInfo to Position object.

Also rewritten and simplified Position constructors.

Verified it does not regress with a 3 threads SMP test:
ELO: -0.00 +-12.7 (95%) LOS: 50.0%
Total: 1000 W: 173 L: 173 D: 654

No functional change.

12 files changed:
src/benchmark.cpp
src/endgame.cpp
src/position.cpp
src/position.h
src/search.cpp
src/search.h
src/syzygy/tbprobe.cpp
src/syzygy/tbprobe.h
src/thread.cpp
src/thread.h
src/timeman.cpp
src/uci.cpp

index 6d69541..2978f34 100644 (file)
@@ -144,10 +144,12 @@ void benchmark(const Position& current, istream& is) {
 
   uint64_t nodes = 0;
   TimePoint elapsed = now();
+  Position pos;
 
   for (size_t i = 0; i < fens.size(); ++i)
   {
-      Position pos(fens[i], Options["UCI_Chess960"], Threads.main());
+      StateListPtr states(new std::vector<StateInfo>(1));
+      pos.set(fens[i], Options["UCI_Chess960"], &states->back(), Threads.main());
 
       cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl;
 
@@ -156,9 +158,8 @@ void benchmark(const Position& current, istream& is) {
 
       else
       {
-          Search::StateStackPtr st;
           limits.startTime = now();
-          Threads.start_thinking(pos, limits, st);
+          Threads.start_thinking(pos, states, limits);
           Threads.main()->wait_for_search_finished();
           nodes += Threads.nodes_searched();
       }
index cc66304..04469e8 100644 (file)
@@ -99,7 +99,8 @@ namespace {
     string fen =  sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/"
                 + sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10";
 
-    return Position(fen, false, nullptr).material_key();
+    StateInfo st;
+    return Position().set(fen, false, &st, nullptr).material_key();
   }
 
 } // namespace
index f42377d..6e8b03d 100644 (file)
@@ -155,42 +155,11 @@ void Position::init() {
 }
 
 
-/// Position::operator=() creates a copy of 'pos' but detaching the state pointer
-/// from the source to be self-consistent and not depending on any external data.
-
-Position& Position::operator=(const Position& pos) {
-
-  std::memcpy(this, &pos, sizeof(Position));
-  std::memcpy(&startState, st, sizeof(StateInfo));
-  st = &startState;
-  nodes = 0;
-
-  assert(pos_is_ok());
-
-  return *this;
-}
-
-
-/// Position::clear() erases the position object to a pristine state, with an
-/// empty board, white to move, and no castling rights.
-
-void Position::clear() {
-
-  std::memset(this, 0, sizeof(Position));
-  startState.epSquare = SQ_NONE;
-  st = &startState;
-
-  for (int i = 0; i < PIECE_TYPE_NB; ++i)
-      for (int j = 0; j < 16; ++j)
-          pieceList[WHITE][i][j] = pieceList[BLACK][i][j] = SQ_NONE;
-}
-
-
 /// Position::set() initializes the position object with the given FEN string.
 /// This function is not very robust - make sure that input FENs are correct,
 /// this is assumed to be the responsibility of the GUI.
 
-void Position::set(const string& fenStr, bool isChess960, Thread* th) {
+Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
 /*
    A FEN string defines a particular position using only the ASCII character set.
 
@@ -230,7 +199,11 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) {
   Square sq = SQ_A8;
   std::istringstream ss(fenStr);
 
-  clear();
+  std::memset(this, 0, sizeof(Position));
+  std::memset(si, 0, sizeof(StateInfo));
+  std::fill_n(&pieceList[0][0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
+  st = si;
+
   ss >> std::noskipws;
 
   // 1. Piece placement
@@ -291,6 +264,8 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) {
       if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN)))
           st->epSquare = SQ_NONE;
   }
+  else
+      st->epSquare = SQ_NONE;
 
   // 5-6. Halfmove clock and fullmove number
   ss >> std::skipws >> st->rule50 >> gamePly;
@@ -304,6 +279,8 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) {
   set_state(st);
 
   assert(pos_is_ok());
+
+  return *this;
 }
 
 
@@ -1108,7 +1085,7 @@ void Position::flip() {
   std::getline(ss, token); // Half and full moves
   f += token;
 
-  set(f, is_chess960(), this_thread());
+  set(f, is_chess960(), st, this_thread());
 
   assert(pos_is_ok());
 }
index 1070236..d44ed00 100644 (file)
@@ -23,7 +23,9 @@
 
 #include <cassert>
 #include <cstddef>  // For offsetof()
+#include <memory>   // For std::unique_ptr
 #include <string>
+#include <vector>
 
 #include "bitboard.h"
 #include "types.h"
@@ -75,6 +77,8 @@ struct StateInfo {
   StateInfo* previous;
 };
 
+typedef std::unique_ptr<std::vector<StateInfo>> StateListPtr;
+
 
 /// Position class stores information regarding the board representation as
 /// pieces, side to move, hash keys, castling info, etc. Important methods are
@@ -86,14 +90,12 @@ class Position {
 public:
   static void init();
 
-  Position() = default; // To define the global object RootPos
+  Position() = default;
   Position(const Position&) = delete;
-  Position(const Position& pos, Thread* th) { *this = pos; thisThread = th; }
-  Position(const std::string& f, bool c960, Thread* th) { set(f, c960, th); }
-  Position& operator=(const Position&); // To assign RootPos from UCI
+  Position& operator=(const Position&) = delete;
 
   // FEN string input/output
-  void set(const std::string& fenStr, bool isChess960, Thread* th);
+  Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
   const std::string fen() const;
 
   // Position representation
@@ -178,7 +180,6 @@ public:
 
 private:
   // Initialization helpers (used while setting up a position)
-  void clear();
   void set_castling_right(Color c, Square rfrom);
   void set_state(StateInfo* si) const;
 
@@ -200,7 +201,6 @@ private:
   int castlingRightsMask[SQUARE_NB];
   Square castlingRookSquare[CASTLING_RIGHT_NB];
   Bitboard castlingPath[CASTLING_RIGHT_NB];
-  StateInfo startState;
   uint64_t nodes;
   int gamePly;
   Color sideToMove;
index 10d491c..3176453 100644 (file)
@@ -40,7 +40,6 @@ namespace Search {
 
   SignalsType Signals;
   LimitsType Limits;
-  StateStackPtr SetupStates;
 }
 
 namespace Tablebases {
@@ -317,16 +316,8 @@ void MainThread::search() {
       }
 
       for (Thread* th : Threads)
-      {
-          th->maxPly = 0;
-          th->rootDepth = DEPTH_ZERO;
           if (th != this)
-          {
-              th->rootPos = Position(rootPos, th);
-              th->rootMoves = rootMoves;
               th->start_searching();
-          }
-      }
 
       Thread::search(); // Let's start searching!
   }
@@ -1488,7 +1479,7 @@ moves_loop: // When in check search starts from here
 
   Move Skill::pick_best(size_t multiPV) {
 
-    const Search::RootMoveVector& rootMoves = Threads.main()->rootMoves;
+    const RootMoves& rootMoves = Threads.main()->rootMoves;
     static PRNG rng(now()); // PRNG sequence should be non-deterministic
 
     // RootMoves are already sorted by score in descending order
@@ -1553,7 +1544,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
 
   std::stringstream ss;
   int elapsed = Time.elapsed() + 1;
-  const Search::RootMoveVector& rootMoves = pos.this_thread()->rootMoves;
+  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());
   uint64_t nodes_searched = Threads.nodes_searched();
index 8442037..d722af1 100644 (file)
@@ -22,8 +22,6 @@
 #define SEARCH_H_INCLUDED
 
 #include <atomic>
-#include <memory>  // For std::unique_ptr
-#include <stack>
 #include <vector>
 
 #include "misc.h"
@@ -65,7 +63,7 @@ struct RootMove {
   std::vector<Move> pv;
 };
 
-typedef std::vector<RootMove> RootMoveVector;
+typedef std::vector<RootMove> RootMoves;
 
 /// LimitsType struct stores information sent by GUI about available time to
 /// search the current move, maximum depth/time, if we are in analysis mode or
@@ -74,8 +72,8 @@ typedef std::vector<RootMove> RootMoveVector;
 struct LimitsType {
 
   LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC
-    nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movestogo =
-    depth = movetime = mate = infinite = ponder = 0;
+    nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] =
+    npmsec = movestogo = depth = movetime = mate = infinite = ponder = 0;
   }
 
   bool use_time_management() const {
@@ -95,11 +93,8 @@ struct SignalsType {
   std::atomic_bool stop, stopOnPonderhit;
 };
 
-typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;
-
 extern SignalsType Signals;
 extern LimitsType Limits;
-extern StateStackPtr SetupStates;
 
 void init();
 void clear();
index 14d34e7..e07cd1b 100644 (file)
@@ -688,7 +688,7 @@ static Value wdl_to_Value[5] = {
 //
 // A return value false indicates that not all probes were successful and that
 // no moves were filtered out.
-bool Tablebases::root_probe(Position& pos, Search::RootMoveVector& rootMoves, Value& score)
+bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score)
 {
   int success;
 
@@ -795,7 +795,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoveVector& rootMoves, Va
 //
 // A return value false indicates that not all probes were successful and that
 // no moves were filtered out.
-bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoveVector& rootMoves, Value& score)
+bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score)
 {
   int success;
 
index 4233e1a..2bcc083 100644 (file)
@@ -10,8 +10,8 @@ extern int MaxCardinality;
 void init(const std::string& path);
 int probe_wdl(Position& pos, int *success);
 int probe_dtz(Position& pos, int *success);
-bool root_probe(Position& pos, Search::RootMoveVector& rootMoves, Value& score);
-bool root_probe_wdl(Position& pos, Search::RootMoveVector& rootMoves, Value& score);
+bool root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score);
+bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score);
 
 }
 
index 440b8bf..dc4ec05 100644 (file)
@@ -26,8 +26,6 @@
 #include "thread.h"
 #include "uci.h"
 
-using namespace Search;
-
 ThreadPool Threads; // Global object
 
 /// Thread constructor launches the thread and then waits until it goes to sleep
@@ -171,26 +169,34 @@ int64_t ThreadPool::nodes_searched() {
 /// ThreadPool::start_thinking() wakes up the main thread sleeping in idle_loop()
 /// and starts a new search, then returns immediately.
 
-void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits,
-                                StateStackPtr& states) {
+void ThreadPool::start_thinking(const Position& pos, StateListPtr& states,
+                                const Search::LimitsType& limits) {
 
   main()->wait_for_search_finished();
 
-  Signals.stopOnPonderhit = Signals.stop = false;
-
-  main()->rootMoves.clear();
-  main()->rootPos = pos;
-  Limits = limits;
-  if (states.get()) // If we don't set a new position, preserve current state
-  {
-      SetupStates = std::move(states); // Ownership transfer here
-      assert(!states.get());
-  }
+  Search::Signals.stopOnPonderhit = Search::Signals.stop = false;
+  Search::Limits = limits;
+  Search::RootMoves rootMoves;
 
   for (const auto& m : MoveList<LEGAL>(pos))
       if (   limits.searchmoves.empty()
           || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
-          main()->rootMoves.push_back(RootMove(m));
+          rootMoves.push_back(Search::RootMove(m));
+
+  // After ownership transfer 'states' becomes empty, so if we stop the search
+  // and call 'go' again without setting a new position states.get() == NULL.
+  assert(states.get() || setupStates.get());
+
+  if (states.get())
+      setupStates = std::move(states); // Ownership transfer, states is now empty
+
+  for (Thread* th : Threads)
+  {
+      th->maxPly = 0;
+      th->rootDepth = DEPTH_ZERO;
+      th->rootMoves = rootMoves;
+      th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
+  }
 
   main()->start_searching();
 }
index 43ddfbb..0b112de 100644 (file)
@@ -64,7 +64,7 @@ public:
   int maxPly, callsCnt;
 
   Position rootPos;
-  Search::RootMoveVector rootMoves;
+  Search::RootMoves rootMoves;
   Depth rootDepth;
   HistoryStats history;
   MoveStats counterMoves;
@@ -94,9 +94,12 @@ struct ThreadPool : public std::vector<Thread*> {
   void exit(); // be initialized and valid during the whole thread lifetime.
 
   MainThread* main() { return static_cast<MainThread*>(at(0)); }
-  void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&);
+  void start_thinking(const Position&, StateListPtr&, const Search::LimitsType&);
   void read_uci_options();
   int64_t nodes_searched();
+
+private:
+  StateListPtr setupStates;
 };
 
 extern ThreadPool Threads;
index bc9c2e5..c7c19f4 100644 (file)
@@ -33,7 +33,7 @@ namespace {
   enum TimeType { OptimumTime, MaxTime };
 
   const int MoveHorizon   = 50;   // Plan time management at most this many moves ahead
-  const double MaxRatio   = 7.09;  // When in trouble, we can step over reserved time with this ratio
+  const double MaxRatio   = 7.09; // When in trouble, we can step over reserved time with this ratio
   const double StealRatio = 0.35; // However we must not steal time from remaining moves over this ratio
 
 
index 4d12ca4..cfd253b 100644 (file)
@@ -39,10 +39,10 @@ namespace {
   // FEN string of the initial position, normal chess
   const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
 
-  // Stack to keep track of the position states along the setup moves (from the
+  // A list to keep track of the position states along the setup moves (from the
   // start position to the position just before the search starts). Needed by
   // 'draw by repetition' detection.
-  Search::StateStackPtr SetupStates;
+  StateListPtr States(new std::vector<StateInfo>(1));
 
 
   // position() is called when engine receives the "position" UCI command.
@@ -68,14 +68,14 @@ namespace {
     else
         return;
 
-    pos.set(fen, Options["UCI_Chess960"], Threads.main());
-    SetupStates = Search::StateStackPtr(new std::stack<StateInfo>);
+    States = StateListPtr(new std::vector<StateInfo>(1));
+    pos.set(fen, Options["UCI_Chess960"], &States->back(), Threads.main());
 
     // Parse move list (if any)
     while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
     {
-        SetupStates->push(StateInfo());
-        pos.do_move(m, SetupStates->top(), pos.gives_check(m, CheckInfo(pos)));
+        States->push_back(StateInfo());
+        pos.do_move(m, States->back(), pos.gives_check(m, CheckInfo(pos)));
     }
   }
 
@@ -132,7 +132,7 @@ namespace {
         else if (token == "infinite")  limits.infinite = 1;
         else if (token == "ponder")    limits.ponder = 1;
 
-    Threads.start_thinking(pos, limits, SetupStates);
+    Threads.start_thinking(pos, States, limits);
   }
 
 } // namespace
@@ -146,9 +146,11 @@ namespace {
 
 void UCI::loop(int argc, char* argv[]) {
 
-  Position pos(StartFEN, false, Threads.main()); // The root position
+  Position pos;
   string token, cmd;
 
+  pos.set(StartFEN, false, &States->back(), Threads.main());
+
   for (int i = 1; i < argc; ++i)
       cmd += std::string(argv[i]) + " ";