Skip to content

Commit 073eed5

Browse files
atumanianmcostalba
authored andcommitted
Optimisation of Position::see and Position::see_sign
Stephane's patch removes the only usage of Position::see, where the returned value isn't immediately compared with a value. So I replaced this function by its optimised and more specific version see_ge. This function also supersedes the function Position::see_sign. bool Position::see_ge(Move m, Value v) const; This function tests if the SEE of a move is greater or equal than a given value. We use forward iteration on captures instread of backward one, therefore we don't need the swapList array. Also we stop as soon as we have enough information to obtain the result, avoiding unnecessary calls to the min_attacker function. Speed tests (Windows 7), 20 runs for each engine: Test engine: mean 866648, st. dev. 5964 Base engine: mean 846751, st. dev. 22846 Speedup: 1.023 Speed test by Stephane Nicolet Fishtest STC test: LLR: 2.96 (-2.94,2.94) [0.00,5.00] Total: 26040 W: 4675 L: 4442 D: 16923 http://tests.stockfishchess.org/tests/view/57f648990ebc59038170fa03 No functional change.
1 parent 1e58628 commit 073eed5

File tree

4 files changed

+63
-83
lines changed

4 files changed

+63
-83
lines changed

src/movepick.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th)
115115
ttMove = ttm
116116
&& pos.pseudo_legal(ttm)
117117
&& pos.capture(ttm)
118-
&& pos.see(ttm) > threshold ? ttm : MOVE_NONE;
118+
&& pos.see_ge(ttm, threshold + 1)? ttm : MOVE_NONE;
119119

120120
stage += (ttMove == MOVE_NONE);
121121
}
@@ -201,7 +201,7 @@ Move MovePicker::next_move() {
201201
move = pick_best(cur++, endMoves);
202202
if (move != ttMove)
203203
{
204-
if (pos.see_sign(move) >= VALUE_ZERO)
204+
if (pos.see_ge(move, VALUE_ZERO))
205205
return move;
206206

207207
// Losing capture, move it to the beginning of the array
@@ -295,7 +295,7 @@ Move MovePicker::next_move() {
295295
{
296296
move = pick_best(cur++, endMoves);
297297
if ( move != ttMove
298-
&& pos.see(move) > threshold)
298+
&& pos.see_ge(move, threshold + 1))
299299
return move;
300300
}
301301
break;

src/position.cpp

Lines changed: 53 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -955,102 +955,83 @@ Key Position::key_after(Move m) const {
955955
}
956956

957957

958-
/// Position::see() is a static exchange evaluator: It tries to estimate the
959-
/// material gain or loss resulting from a move.
958+
/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the
959+
/// SEE value of move is greater or equal to the given value. We'll use an
960+
/// algorithm similar to alpha-beta pruning with a null window.
960961

961-
Value Position::see_sign(Move m) const {
962+
bool Position::see_ge(Move m, Value v) const {
962963

963964
assert(is_ok(m));
964965

965-
// Early return if SEE cannot be negative because captured piece value
966-
// is not less then capturing one. Note that king moves always return
967-
// here because king midgame value is set to 0.
968-
if (PieceValue[MG][moved_piece(m)] <= PieceValue[MG][piece_on(to_sq(m))])
969-
return VALUE_KNOWN_WIN;
970-
971-
return see(m);
972-
}
973-
974-
Value Position::see(Move m) const {
975-
976-
Square from, to;
977-
Bitboard occupied, attackers, stmAttackers;
978-
Value swapList[32];
979-
int slIndex = 1;
980-
PieceType nextVictim;
981-
Color stm;
982-
983-
assert(is_ok(m));
984-
985-
from = from_sq(m);
986-
to = to_sq(m);
987-
swapList[0] = PieceValue[MG][piece_on(to)];
988-
stm = color_of(piece_on(from));
989-
occupied = pieces() ^ from;
990-
991-
// Castling moves are implemented as king capturing the rook so cannot
992-
// be handled correctly. Simply return VALUE_ZERO that is always correct
993-
// unless in the rare case the rook ends up under attack.
966+
// Castling moves are implemented as king capturing the rook so cannot be
967+
// handled correctly. Simply assume the SEE value is VALUE_ZERO that is always
968+
// correct unless in the rare case the rook ends up under attack.
994969
if (type_of(m) == CASTLING)
995-
return VALUE_ZERO;
970+
return VALUE_ZERO >= v;
971+
972+
Square from = from_sq(m), to = to_sq(m);
973+
PieceType nextVictim = type_of(piece_on(from));
974+
Color stm = ~color_of(piece_on(from)); // First consider opponent's move
975+
Value balance; // Values of the pieces taken by us minus opponent's ones
976+
Bitboard occupied, stmAttackers;
996977

997978
if (type_of(m) == ENPASSANT)
998979
{
999-
occupied ^= to - pawn_push(stm); // Remove the captured pawn
1000-
swapList[0] = PieceValue[MG][PAWN];
980+
occupied = SquareBB[to - pawn_push(~stm)]; // Remove the captured pawn
981+
balance = PieceValue[MG][PAWN];
982+
}
983+
else
984+
{
985+
balance = PieceValue[MG][piece_on(to)];
986+
occupied = 0;
1001987
}
1002988

1003-
// Find all attackers to the destination square, with the moving piece
1004-
// removed, but possibly an X-ray attacker added behind it.
1005-
attackers = attackers_to(to, occupied) & occupied;
989+
if (balance < v)
990+
return false;
1006991

1007-
// If the opponent has no attackers we are finished
1008-
stm = ~stm;
1009-
stmAttackers = attackers & pieces(stm);
1010-
occupied ^= to; // For the case when captured piece is a pinner
992+
if (nextVictim == KING)
993+
return true;
994+
995+
balance -= PieceValue[MG][nextVictim];
996+
997+
if (balance >= v)
998+
return true;
1011999

1012-
// Don't allow pinned pieces to attack pieces except the king as long all
1013-
// pinners are on their original square.
1014-
if (!(st->pinnersForKing[stm] & ~occupied))
1015-
stmAttackers &= ~st->blockersForKing[stm];
1000+
bool relativeStm = true; // True if the opponent is to move
1001+
occupied ^= pieces() ^ from ^ to;
10161002

1017-
if (!stmAttackers)
1018-
return swapList[0];
1003+
// Find all attackers to the destination square, with the moving piece removed,
1004+
// but possibly an X-ray attacker added behind it.
1005+
Bitboard attackers = attackers_to(to, occupied) & occupied;
10191006

1020-
// The destination square is defended, which makes things rather more
1021-
// difficult to compute. We proceed by building up a "swap list" containing
1022-
// the material gain or loss at each stop in a sequence of captures to the
1023-
// destination square, where the sides alternately capture, and always
1024-
// capture with the least valuable piece. After each capture, we look for
1025-
// new X-ray attacks from behind the capturing piece.
1026-
nextVictim = type_of(piece_on(from));
1007+
while (true)
1008+
{
1009+
stmAttackers = attackers & pieces(stm);
10271010

1028-
do {
1029-
assert(slIndex < 32);
1011+
// Don't allow pinned pieces to attack pieces except the king as long all
1012+
// pinners are on their original square.
1013+
if (!(st->pinnersForKing[stm] & ~occupied))
1014+
stmAttackers &= ~st->blockersForKing[stm];
10301015

1031-
// Add the new entry to the swap list
1032-
swapList[slIndex] = -swapList[slIndex - 1] + PieceValue[MG][nextVictim];
1016+
if (!stmAttackers)
1017+
return relativeStm;
10331018

10341019
// Locate and remove the next least valuable attacker
10351020
nextVictim = min_attacker<PAWN>(byTypeBB, to, stmAttackers, occupied, attackers);
1036-
stm = ~stm;
1037-
stmAttackers = attackers & pieces(stm);
10381021

1039-
// Don't allow pinned pieces to attack pieces except the king
1040-
if ( nextVictim != KING
1041-
&& !(st->pinnersForKing[stm] & ~occupied))
1042-
stmAttackers &= ~st->blockersForKing[stm];
1022+
if (nextVictim == KING)
1023+
return relativeStm == bool(attackers & pieces(~stm));
10431024

1044-
++slIndex;
1025+
balance += relativeStm ? PieceValue[MG][nextVictim]
1026+
: -PieceValue[MG][nextVictim];
10451027

1046-
} while (stmAttackers && (nextVictim != KING || (--slIndex, false))); // Stop before a king capture
1028+
relativeStm = !relativeStm;
10471029

1048-
// Having built the swap list, we negamax through it to find the best
1049-
// achievable score from the point of view of the side to move.
1050-
while (--slIndex)
1051-
swapList[slIndex - 1] = std::min(-swapList[slIndex], swapList[slIndex - 1]);
1030+
if (relativeStm == (balance >= v))
1031+
return relativeStm;
10521032

1053-
return swapList[0];
1033+
stm = ~stm;
1034+
}
10541035
}
10551036

10561037

src/position.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,7 @@ class Position {
133133
void undo_null_move();
134134

135135
// Static Exchange Evaluation
136-
Value see(Move m) const;
137-
Value see_sign(Move m) const;
136+
bool see_ge(Move m, Value value) const;
138137

139138
// Accessing hash keys
140139
Key key() const;

src/search.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ namespace {
889889
// Step 12. Extend checks
890890
if ( givesCheck
891891
&& !moveCountPruning
892-
&& pos.see_sign(move) >= VALUE_ZERO)
892+
&& pos.see_ge(move, VALUE_ZERO))
893893
extension = ONE_PLY;
894894

895895
// Singular extension search. If all moves but one fail low on a search of
@@ -946,11 +946,11 @@ namespace {
946946

947947
// Prune moves with negative SEE
948948
if ( lmrDepth < 8
949-
&& pos.see_sign(move) < Value(-35 * lmrDepth * lmrDepth))
949+
&& !pos.see_ge(move, Value(-35 * lmrDepth * lmrDepth)))
950950
continue;
951951
}
952952
else if ( depth < 7 * ONE_PLY
953-
&& pos.see_sign(move) < Value(-35 * depth / ONE_PLY * depth / ONE_PLY))
953+
&& !pos.see_ge(move, Value(-35 * depth / ONE_PLY * depth / ONE_PLY)))
954954
continue;
955955
}
956956

@@ -992,7 +992,7 @@ namespace {
992992
// because the destination square is empty.
993993
else if ( type_of(move) == NORMAL
994994
&& type_of(pos.piece_on(to_sq(move))) != PAWN
995-
&& pos.see(make_move(to_sq(move), from_sq(move))) < VALUE_ZERO)
995+
&& !pos.see_ge(make_move(to_sq(move), from_sq(move)), VALUE_ZERO))
996996
r -= 2 * ONE_PLY;
997997

998998
// Decrease/increase reduction for moves with a good/bad history
@@ -1302,7 +1302,7 @@ namespace {
13021302
continue;
13031303
}
13041304

1305-
if (futilityBase <= alpha && pos.see(move) <= VALUE_ZERO)
1305+
if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1))
13061306
{
13071307
bestValue = std::max(bestValue, futilityBase);
13081308
continue;
@@ -1317,7 +1317,7 @@ namespace {
13171317
// Don't search moves with negative SEE values
13181318
if ( (!InCheck || evasionPrunable)
13191319
&& type_of(move) != PROMOTION
1320-
&& pos.see_sign(move) < VALUE_ZERO)
1320+
&& !pos.see_ge(move, VALUE_ZERO))
13211321
continue;
13221322

13231323
// Speculative prefetch as early as possible

0 commit comments

Comments
 (0)