Skip to content

Commit

Permalink
Join common code in the stages of next_move()
Browse files Browse the repository at this point in the history
Rewrite the MovePicker class using lambda expressions for move filtering.
Includes code style changes by @mcostalba.

Verified for speed, passed STC:
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 43191 W: 9391 L: 9312 D: 24488
http://tests.stockfishchess.org/tests/view/5a99b9df0ebc590297cc8f04

This rewrite of MovePicker.cpp seems to trigger less random crashes on Ryzen
machines than the version in previous master (reported by Bojun Guo).

Closes #1454

No functional change.
  • Loading branch information
vondele authored and snicolet committed Mar 18, 2018
1 parent 1940485 commit bd59560
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 111 deletions.
193 changes: 83 additions & 110 deletions src/movepick.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@
namespace {

enum Stages {
MAIN_SEARCH, CAPTURES_INIT, GOOD_CAPTURES, KILLER0, KILLER1, COUNTERMOVE, QUIET_INIT, QUIET, BAD_CAPTURES,
EVASION, EVASIONS_INIT, ALL_EVASIONS,
PROBCUT, PROBCUT_CAPTURES_INIT, PROBCUT_CAPTURES,
QSEARCH, QCAPTURES_INIT, QCAPTURES, QCHECKS
MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, KILLER0, KILLER1, COUNTERMOVE, QUIET_INIT, QUIET, BAD_CAPTURE,
EVASION_TT, EVASION_INIT, EVASION,
PROBCUT_TT, PROBCUT_INIT, PROBCUT,
QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK
};

// Helper filter used with select_move()
const auto Any = [](){ return true; };

// partial_insertion_sort() sorts moves in descending order up to and including
// a given limit. The order of moves smaller than the limit is left unspecified.
void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
Expand All @@ -46,15 +49,6 @@ namespace {
}
}

// pick_best() finds the best move in the range (begin, end) and moves it to
// the front. It's faster than sorting all the moves in advance when there
// are few moves, e.g., the possible captures.
Move pick_best(ExtMove* begin, ExtMove* end) {

std::swap(*begin, *std::max_element(begin, end));
return *begin;
}

} // namespace


Expand All @@ -72,18 +66,19 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist

assert(d > DEPTH_ZERO);

stage = pos.checkers() ? EVASION : MAIN_SEARCH;
stage = pos.checkers() ? EVASION_TT : MAIN_TT;
ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
stage += (ttMove == MOVE_NONE);
}

/// MovePicker constructor for quiescence search
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, Square s)
: pos(p), mainHistory(mh), captureHistory(cph), recaptureSquare(s), depth(d) {
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
const CapturePieceToHistory* cph, Square rs)
: pos(p), mainHistory(mh), captureHistory(cph), recaptureSquare(rs), depth(d) {

assert(d <= DEPTH_ZERO);

stage = pos.checkers() ? EVASION : QSEARCH;
stage = pos.checkers() ? EVASION_TT : QSEARCH_TT;
ttMove = ttm
&& pos.pseudo_legal(ttm)
&& (depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare) ? ttm : MOVE_NONE;
Expand All @@ -97,17 +92,17 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece

assert(!pos.checkers());

stage = PROBCUT;
stage = PROBCUT_TT;
ttMove = ttm
&& pos.pseudo_legal(ttm)
&& pos.capture(ttm)
&& pos.see_ge(ttm, threshold) ? ttm : MOVE_NONE;
stage += (ttMove == MOVE_NONE);
}

/// score() assigns a numerical value to each move in a list, used for sorting.
/// Captures are ordered by Most Valuable Victim (MVV), preferring captures
/// with a good history. Quiets are ordered using the histories.
/// MovePicker::score() assigns a numerical value to each move in a list, used
/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring
/// captures with a good history. Quiets moves are ordered using the histories.
template<GenType Type>
void MovePicker::score() {

Expand All @@ -134,65 +129,66 @@ void MovePicker::score() {
}
}

/// next_move() is the most important method of the MovePicker class. It returns
/// a new pseudo legal move every time it is called, until there are no more moves
/// left. It picks the move with the biggest value from a list of generated moves
/// taking care not to return the ttMove if it has already been searched.
/// MovePicker::select_move() returns the next move satisfying a predicate function
template<PickType T, typename Pred>
Move MovePicker::select_move(Pred filter) {

Move MovePicker::next_move(bool skipQuiets) {
while (cur < endMoves)
{
if (T == BEST_SCORE)
std::swap(*cur, *std::max_element(cur, endMoves));

Move move;
move = *cur++;

begin_switch:
if (move != ttMove && filter())
return move;
}
return move = MOVE_NONE;
}

/// MovePicker::next_move() is the most important method of the MovePicker class. It
/// returns a new pseudo legal move every time it is called, until there are no more
/// moves left. It picks the move with the highest score from a list of generated moves.
Move MovePicker::next_move(bool skipQuiets) {

top:
switch (stage) {

case MAIN_SEARCH:
case EVASION:
case QSEARCH:
case PROBCUT:
case MAIN_TT:
case EVASION_TT:
case QSEARCH_TT:
case PROBCUT_TT:
++stage;
return ttMove;

case CAPTURES_INIT:
case PROBCUT_CAPTURES_INIT:
case QCAPTURES_INIT:
case CAPTURE_INIT:
case PROBCUT_INIT:
case QCAPTURE_INIT:
endBadCaptures = cur = moves;
endMoves = generate<CAPTURES>(pos, cur);
score<CAPTURES>();
++stage;
goto top;

// Rebranch at the top of the switch
goto begin_switch;

case GOOD_CAPTURES:
while (cur < endMoves)
{
move = pick_best(cur++, endMoves);
if (move != ttMove)
{
if (pos.see_ge(move, Value(-55 * (cur-1)->value / 1024)))
return move;

// Losing capture, move it to the beginning of the array
*endBadCaptures++ = move;
}
}
++stage;
case GOOD_CAPTURE:
if (select_move<BEST_SCORE>([&](){ return pos.see_ge(move, Value(-55 * (cur-1)->value / 1024)) ?
// Move losing capture to endBadCaptures to be tried later
true : (*endBadCaptures++ = move, false); }))
return move;

// If the countermove is the same as a killer, skip it
if ( refutations[0] == refutations[2]
|| refutations[1] == refutations[2])
refutations[2] = MOVE_NONE;

refutations[2] = MOVE_NONE;
++stage;
/* fallthrough */

case KILLER0:
case KILLER1:
case COUNTERMOVE:
while (stage <= COUNTERMOVE)
{
move = refutations[ stage++ - KILLER0 ];
move = refutations[ stage++ - KILLER0];
if ( move != MOVE_NONE
&& move != ttMove
&& pos.pseudo_legal(move)
Expand All @@ -210,78 +206,55 @@ Move MovePicker::next_move(bool skipQuiets) {
/* fallthrough */

case QUIET:
if (!skipQuiets)
while (cur < endMoves)
{
move = *cur++;
if ( move != ttMove
&& move != refutations[0]
&& move != refutations[1]
&& move != refutations[2])
return move;
}
if ( !skipQuiets
&& select_move<NEXT>([&](){return move != refutations[0]
&& move != refutations[1]
&& move != refutations[2];}))
return move;

// Point to beginning and end of bad captures
cur = moves, endMoves = endBadCaptures;
++stage;
cur = moves; // Point to beginning of bad captures
/* fallthrough */

case BAD_CAPTURES:
if (cur < endBadCaptures)
return *cur++;
break;
case BAD_CAPTURE:
return select_move<NEXT>(Any);

case EVASIONS_INIT:
case EVASION_INIT:
cur = moves;
endMoves = generate<EVASIONS>(pos, cur);
score<EVASIONS>();
++stage;
/* fallthrough */

case ALL_EVASIONS:
while (cur < endMoves)
{
move = pick_best(cur++, endMoves);
if (move != ttMove)
return move;
}
break;
case EVASION:
return select_move<BEST_SCORE>(Any);

case PROBCUT_CAPTURES:
while (cur < endMoves)
{
move = pick_best(cur++, endMoves);
if ( move != ttMove
&& pos.see_ge(move, threshold))
return move;
}
break;
case PROBCUT:
return select_move<BEST_SCORE>([&](){ return pos.see_ge(move, threshold); });

case QCAPTURES:
while (cur < endMoves)
{
move = pick_best(cur++, endMoves);
if ( move != ttMove
&& (depth > DEPTH_QS_RECAPTURES || to_sq(move) == recaptureSquare))
return move;
}
if (depth <= DEPTH_QS_NO_CHECKS)
break;
case QCAPTURE:
if (select_move<BEST_SCORE>([&](){ return depth > DEPTH_QS_RECAPTURES
|| to_sq(move) == recaptureSquare; }))
return move;

// If we did not find any move and we do not try checks, we have finished
if (depth != DEPTH_QS_CHECKS)
return MOVE_NONE;

++stage;
/* fallthrough */

case QCHECK_INIT:
cur = moves;
endMoves = generate<QUIET_CHECKS>(pos, cur);
++stage;
/* fallthrough */

case QCHECKS:
while (cur < endMoves)
{
move = *cur++;
if (move != ttMove)
return move;
}
break;

default:
assert(false);
case QCHECK:
return select_move<NEXT>(Any);
}

return MOVE_NONE;
assert(false);
return MOVE_NONE; // Silence warning
}
6 changes: 5 additions & 1 deletion src/movepick.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class StatsEntry {
void operator<<(int bonus) {

assert(abs(bonus) <= D); // Ensure range is [-W * D, W * D]
assert(abs(W * D) < std::numeric_limits<T>::max()); // Ensure we don't overflow
assert(W * D < std::numeric_limits<T>::max()); // Ensure we don't overflow

entry += bonus * W - entry * abs(bonus) / D;

Expand Down Expand Up @@ -110,6 +110,8 @@ typedef Stats<PieceToHistory, W32, NOT_USED, PIECE_NB, SQUARE_NB> ContinuationHi
/// beta algorithm, MovePicker attempts to return the moves which are most likely
/// to get a cut-off first.

enum PickType { NEXT, BEST_SCORE };

class MovePicker {
public:
MovePicker(const MovePicker&) = delete;
Expand All @@ -120,6 +122,7 @@ class MovePicker {
Move next_move(bool skipQuiets = false);

private:
template<PickType T, typename Pred> Move select_move(Pred);
template<GenType> void score();
ExtMove* begin() { return cur; }
ExtMove* end() { return endMoves; }
Expand All @@ -131,6 +134,7 @@ class MovePicker {
Move ttMove, refutations[3];
ExtMove *cur, *endMoves, *endBadCaptures;
int stage;
Move move;
Square recaptureSquare;
Value threshold;
Depth depth;
Expand Down

0 comments on commit bd59560

Please sign in to comment.