@@ -90,47 +90,48 @@ namespace {
9090 Move best = MOVE_NONE;
9191 };
9292
93- struct FastMove {
94- FastMove () { clear (); }
93+ // EasyMoveManager struct is used to detect a so called 'easy move'; when PV is
94+ // stable across multiple search iterations we can fast return the best move.
95+ struct EasyMoveManager {
9596
96- inline void clear () {
97- expectedPosKey = 0 ;
98- pv3[0 ] = pv3[1 ] = pv3[2 ] = MOVE_NONE;
97+ void clear () {
9998 stableCnt = 0 ;
99+ expectedPosKey = 0 ;
100+ pv[0 ] = pv[1 ] = pv[2 ] = MOVE_NONE;
100101 }
101102
102- void update (Position& pos) {
103- // Keep track how many times in a row the PV stays stable 3 ply deep.
104- const std::vector<Move>& RMpv = RootMoves[0 ].pv ;
105- if (RMpv.size () >= 3 )
106- {
107- if (pv3[2 ] == RMpv[2 ])
108- stableCnt++;
109- else
110- stableCnt = 0 , pv3[2 ] = RMpv[2 ];
103+ Move get (Key key) const {
104+ return expectedPosKey == key ? pv[2 ] : MOVE_NONE;
105+ }
111106
112- if (!expectedPosKey || pv3[0 ] != RMpv[0 ] || pv3[1 ] != RMpv[1 ])
113- {
114- pv3[0 ] = RMpv[0 ], pv3[1 ] = RMpv[1 ];
115- StateInfo st[2 ];
116- pos.do_move (RMpv[0 ], st[0 ], pos.gives_check (RMpv[0 ], CheckInfo (pos)));
117- pos.do_move (RMpv[1 ], st[1 ], pos.gives_check (RMpv[1 ], CheckInfo (pos)));
118- expectedPosKey = pos.key ();
119- pos.undo_move (RMpv[1 ]);
120- pos.undo_move (RMpv[0 ]);
121- }
107+ void update (Position& pos, const std::vector<Move>& newPv) {
108+
109+ assert (newPv.size () >= 3 );
110+
111+ // Keep track of how many times in a row 3rd ply remains stable
112+ stableCnt = (newPv[2 ] == pv[2 ]) ? stableCnt + 1 : 0 ;
113+
114+ if (!std::equal (newPv.begin (), newPv.begin () + 3 , pv))
115+ {
116+ std::copy (newPv.begin (), newPv.begin () + 3 , pv);
117+
118+ StateInfo st[2 ];
119+ pos.do_move (newPv[0 ], st[0 ], pos.gives_check (newPv[0 ], CheckInfo (pos)));
120+ pos.do_move (newPv[1 ], st[1 ], pos.gives_check (newPv[1 ], CheckInfo (pos)));
121+ expectedPosKey = pos.key ();
122+ pos.undo_move (newPv[1 ]);
123+ pos.undo_move (newPv[0 ]);
122124 }
123- else
124- clear ();
125125 }
126126
127- Key expectedPosKey;
128- Move pv3[3 ];
129127 int stableCnt;
130- } FM;
128+ Key expectedPosKey;
129+ Move pv[3 ];
130+ };
131131
132132 size_t PVIdx;
133133 TimeManager TimeMgr;
134+ EasyMoveManager EasyMove;
134135 double BestMoveChanges;
135136 Value DrawValue[COLOR_NB];
136137 HistoryStats History;
@@ -323,9 +324,8 @@ namespace {
323324 Depth depth;
324325 Value bestValue, alpha, beta, delta;
325326
326- // Init fastMove if the previous search generated a candidate and we now got the predicted position.
327- const Move fastMove = (FM.expectedPosKey == pos.key ()) ? FM.pv3 [2 ] : MOVE_NONE;
328- FM.clear ();
327+ Move easyMove = EasyMove.get (pos.key ());
328+ EasyMove.clear ();
329329
330330 std::memset (ss-2 , 0 , 5 * sizeof (Stack));
331331
@@ -460,13 +460,13 @@ namespace {
460460 TimeMgr.pv_instability (BestMoveChanges);
461461
462462 // Stop the search if only one legal move is available or all
463- // of the available time has been used or we matched a fastMove
463+ // of the available time has been used or we matched an easyMove
464464 // from the previous search and just did a fast verification.
465465 if ( RootMoves.size () == 1
466466 || now () - SearchTime > TimeMgr.available_time ()
467- || ( fastMove == RootMoves[0 ].pv [0 ]
467+ || ( RootMoves[0 ].pv [0 ] == easyMove
468468 && BestMoveChanges < 0.03
469- && 10 * ( now () - SearchTime) > TimeMgr.available_time ()))
469+ && now () - SearchTime > TimeMgr.available_time () / 10 ))
470470 {
471471 // If we are allowed to ponder do not stop the search now but
472472 // keep pondering until the GUI sends "ponderhit" or "stop".
@@ -477,16 +477,17 @@ namespace {
477477 }
478478 }
479479
480- // Update fast move stats.
481- FM.update (pos);
480+ if (RootMoves[0 ].pv .size () >= 3 )
481+ EasyMove.update (pos, RootMoves[0 ].pv );
482+ else
483+ EasyMove.clear ();
482484 }
483485 }
484486
485- // Clear any candidate fast move that wasn't completely stable for at least
486- // the 6 final search iterations. (Independent of actual depth and thus TC.)
487- // Time condition prevents consecutive fast moves.
488- if (FM.stableCnt < 6 || now () - SearchTime < TimeMgr.available_time ())
489- FM.clear ();
487+ // Clear any candidate easy move that wasn't stable for the last search
488+ // iterations; the second condition prevents consecutive fast moves.
489+ if (EasyMove.stableCnt < 6 || now () - SearchTime < TimeMgr.available_time ())
490+ EasyMove.clear ();
490491
491492 // If skill level is enabled, swap best PV line with the sub-optimal one
492493 if (skill.enabled ())
@@ -1074,9 +1075,11 @@ namespace {
10741075
10751076 if (value > alpha)
10761077 {
1077- // Clear fast move if unstable.
1078- if (PvNode && pos.key () == FM.expectedPosKey && (move != FM.pv3 [2 ] || moveCount > 1 ))
1079- FM.clear ();
1078+ // If there is an easy move for this position, clear it if unstable
1079+ if ( PvNode
1080+ && EasyMove.get (pos.key ())
1081+ && (move != EasyMove.get (pos.key ()) || moveCount > 1 ))
1082+ EasyMove.clear ();
10801083
10811084 bestMove = SpNode ? splitPoint->bestMove = move : move;
10821085
0 commit comments