Skip to content

Commit ae6bc63

Browse files
committed
Implement killer move heuristic.
If a move from ttable is available, regardless of depth requirement, we search this first in the hopes that this will cause more cutoffs. Makes a _huge_ impact on bench score. Very slightly lower nodes/second, but speed is reduced by ~50%. Average of 3 bench results on MacBook Air 2015: v1.6.0: =========================== Total time (ms) : 11530 Nodes searched : 11924519 Nodes/second : 1034217 v1.5.1: =========================== Total time (ms) : 21660 Nodes searched : 23289650 Nodes/second : 1075221
1 parent 7f33e47 commit ae6bc63

File tree

4 files changed

+67
-40
lines changed

4 files changed

+67
-40
lines changed

Diff for: CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
cmake_minimum_required(VERSION 3.0)
1111

1212
# Set project name here.
13-
project(Goldfish VERSION 1.5.1 LANGUAGES CXX)
13+
project(Goldfish VERSION 1.6.0 LANGUAGES CXX)
1414

1515

1616
# Include stuff. No change needed.

Diff for: include/movelist.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class MoveList {
2626
void sort();
2727

2828
void rate_from_Mvvlva();
29+
30+
void add_killer(Move m);
2931
};
3032

3133
class MoveVariation {

Diff for: src/movelist.cpp

+19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <algorithm>
2+
#include <cassert>
13
#include "movelist.hpp"
24

35
namespace goldfish {
@@ -27,6 +29,23 @@ void MoveList<T>::sort() {
2729
}
2830
}
2931

32+
/**
33+
* Move the given move to the front of the array, keeping
34+
* all others in the same order.
35+
*/
36+
template<class T>
37+
void MoveList<T>::add_killer(Move m) {
38+
for (auto killer = entries.rbegin(); killer != entries.rend(); ++killer) {
39+
if ((*killer)->move == m) {
40+
// Right shift the subarray [begin, entry] by one shift.
41+
std::rotate(killer, killer + 1, entries.rend());
42+
return;
43+
}
44+
}
45+
// The move should always be in the list.
46+
assert(false);
47+
}
48+
3049
/**
3150
* Rates the moves in the list according to "Most Valuable Victim - Least Valuable Aggressor".
3251
*/

Diff for: src/search.cpp

+45-39
Original file line numberDiff line numberDiff line change
@@ -393,48 +393,45 @@ void Search::search_root(Depth depth, int alpha, int beta) {
393393
int Search::search(Depth depth, int alpha, int beta, int ply) {
394394
// Check TTable before anything else is done.
395395
auto entry = ttable.probe(position.zobrist_key);
396-
if (entry != nullptr) {
397-
if (entry->depth() >= depth) {
398-
399-
switch (entry->bound()) {
400-
401-
case Bound::EXACT:
402-
// In the case of exact scores, we can always return
403-
// right away. Just update best move if we exceed alpha.
404-
if (entry->value() > alpha)
405-
save_pv(entry->move(), pv[ply + 1], pv[ply]);
406-
update_search(ply);
407-
return entry->value();
408-
409-
case Bound::LOWER:
410-
// With a lower bound we check if we should update alpha.
411-
// After all this we
412-
if (entry->value() > alpha) {
413-
save_pv(entry->move(), pv[ply + 1], pv[ply]);
414-
alpha = entry->value();
415-
416-
// Check for zero-size search window.
417-
if (entry->value() >= beta) {
418-
update_search(ply);
419-
return entry->value();
420-
}
421-
}
422-
break;
423-
424-
case Bound::UPPER:
425-
// For upper bounds, we can stop if the bound
426-
// is leq than alpha because no move will improve alpha.
427-
// If alpha < bound we have no usefull info, as the
428-
// exact value could still be less than alpha, so we
429-
// cannot update alpha and pv yet.
430-
if (entry->value() <= alpha) {
396+
if (entry != nullptr and entry->depth() >= depth) {
397+
switch (entry->bound()) {
398+
399+
case Bound::EXACT:
400+
// In the case of exact scores, we can always return
401+
// right away. Just update best move if we exceed alpha.
402+
if (entry->value() > alpha)
403+
save_pv(entry->move(), pv[ply + 1], pv[ply]);
404+
update_search(ply);
405+
return entry->value();
406+
407+
case Bound::LOWER:
408+
// With a lower bound we check if we should update alpha.
409+
// After all this we
410+
if (entry->value() > alpha) {
411+
save_pv(entry->move(), pv[ply + 1], pv[ply]);
412+
alpha = entry->value();
413+
414+
// Check for zero-size search window.
415+
if (entry->value() >= beta) {
431416
update_search(ply);
432417
return entry->value();
433418
}
434-
break;
435-
default:
436-
throw std::exception();
437-
}
419+
}
420+
break;
421+
422+
case Bound::UPPER:
423+
// For upper bounds, we can stop if the bound
424+
// is leq than alpha because no move will improve alpha.
425+
// If alpha < bound we have no usefull info, as the
426+
// exact value could still be less than alpha, so we
427+
// cannot update alpha and pv yet.
428+
if (entry->value() <= alpha) {
429+
update_search(ply);
430+
return entry->value();
431+
}
432+
break;
433+
default:
434+
throw std::exception();
438435
}
439436
}
440437

@@ -500,6 +497,15 @@ int Search::search(Depth depth, int alpha, int beta, int ply) {
500497
}
501498

502499
MoveList<MoveEntry> &moves = move_generators[ply].get_moves(position, depth, is_check);
500+
501+
// Killer Move Heuristic:
502+
// If lookup didn't cause a cutoff, including if we don't have the required depth to use
503+
// the table entry, lets use the stored move as a killer move,
504+
// searching it first in the hopes that it will lead to more cutoffs.
505+
if (entry != nullptr and entry->move() != Move::NO_MOVE) {
506+
moves.add_killer(entry->move());
507+
}
508+
503509
for (int i = 0; i < moves.size; i++) {
504510
Move move = moves.entries[i]->move;
505511
int value = best_value;

0 commit comments

Comments
 (0)