Skip to content

Commit

Permalink
Add parallel search with shared transposition table
Browse files Browse the repository at this point in the history
  • Loading branch information
vinc committed Oct 13, 2017
1 parent 441470e commit 4b2c53e
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 26 deletions.
1 change: 1 addition & 0 deletions src/clock.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
extern crate time;

#[derive(Clone)]
pub struct Clock {
moves_level: u16,
//time_level: u64, // TODO: check that we really don't need it
Expand Down
18 changes: 14 additions & 4 deletions src/game.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::fmt;
use std::sync::Arc;

use common::*;
use clock::Clock;
use moves::{Move, Moves};
use positions::Positions;
use transpositions::Transpositions;
use transpositions::{Transpositions, SharedTranspositions};
use zobrist::Zobrist;
use piece::{PieceAttr, PieceChar};

#[derive(Clone)]
pub struct Game {
pub is_debug: bool, // Print debugging
pub is_verbose: bool, // Print thinking
Expand All @@ -20,7 +22,7 @@ pub struct Game {
pub positions: Positions,
pub zobrist: Zobrist,
pub history: Vec<Move>,
pub tt: Transpositions
pub tt: Arc<SharedTranspositions>
}

impl Game {
Expand All @@ -37,17 +39,25 @@ impl Game {
positions: Positions::new(),
zobrist: Zobrist::new(),
history: Vec::new(),
tt: Transpositions::with_memory(TT_SIZE)
tt: Arc::new(SharedTranspositions::with_memory(TT_SIZE))
}
}

pub fn tt(&self) -> &mut Transpositions {
self.tt.get()
}

pub fn tt_resize(&mut self, memory: usize) {
self.tt = Arc::new(SharedTranspositions::with_memory(memory));
}

pub fn clear(&mut self) {
self.bitboards = [0; 14];
self.board = [EMPTY; 64];
self.moves.clear_all();
self.positions.clear();
self.history.clear();
self.tt.clear();
self.tt().clear();
}

pub fn bitboard(&self, piece: Piece) -> &Bitboard {
Expand Down
3 changes: 1 addition & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ use std::env;
use getopts::Options;

use protocols::cli::CLI;
use transpositions::Transpositions;

fn print_usage(opts: Options) {
let brief = format!("Usage: littlewing [options]");
Expand Down Expand Up @@ -75,7 +74,7 @@ fn main() {
if matches.opt_present("t") {
if let Some(size) = matches.opt_str("t") {
let memory = size.parse::<usize>().unwrap() << 20;
cli.game.tt = Transpositions::with_memory(memory);
cli.game.tt_resize(memory);
}
}
cli.run();
Expand Down
1 change: 1 addition & 0 deletions src/moves.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pub enum MovesStage {
QuietMove = QUIET_MOVE
}

#[derive(Clone)]
pub struct Moves {
killers: [[Move; 2]; MAX_PLY],

Expand Down
1 change: 1 addition & 0 deletions src/positions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ impl Position {
}
}

#[derive(Clone)]
pub struct Positions {
stack: [Position; MAX_POSITIONS],
fullmoves_init: u8,
Expand Down
15 changes: 12 additions & 3 deletions src/protocols/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,20 @@ use moves_generator::MovesGenerator;
use protocols::xboard::XBoard;
use search::Search;

#[derive(Clone)]
pub struct CLI {
pub game: Game,
max_depth: usize,
concurrency: usize,
show_board: bool
}


impl CLI {
pub fn new() -> CLI {
CLI {
game: Game::from_fen(DEFAULT_FEN),
max_depth: MAX_PLY - 10,
concurrency: 1,
show_board: false
}
}
Expand All @@ -57,6 +59,7 @@ impl CLI {
"hide" => { self.cmd_hide(&*args) },
"load" => { self.cmd_setboard(&*args) },
"setboard" => { self.cmd_setboard(&*args) },
"threads" => { self.cmd_threads(&*args) },
"perft" => { self.cmd_perft() },
"perftsuite" => { self.cmd_perftsuite(&*args) },
"testsuite" => { self.cmd_testsuite(&*args) },
Expand All @@ -80,6 +83,7 @@ impl CLI {
println!("hide <feature> Hide <feature>");
println!("time <moves> <time> Set clock to <moves> in <time> (in seconds)");
println!("setboard <fen> Set the board to <fen>");
println!("threads <number> Set the <number> of threads");
println!("perft Count the nodes at each depth");
println!("perftsuite <epd> Compare perft results to each position of <epd>");
println!("testsuite <epd> [<time>] Search each position of <epd> [for <time>]");
Expand Down Expand Up @@ -140,7 +144,7 @@ impl CLI {
}

pub fn cmd_play(&mut self) {
match self.game.root(self.max_depth) {
match self.game.parallel(self.concurrency, self.max_depth) {
None => {
if self.game.is_check(WHITE) {
println!("black mates");
Expand Down Expand Up @@ -247,6 +251,11 @@ impl CLI {
println!("Nodes: {}", nodes_count);
}

pub fn cmd_threads(&mut self, args: &[&str]) {
let n = args[1].parse::<usize>().unwrap();
self.concurrency = n;
}

pub fn cmd_perft(&mut self) {
self.game.moves.skip_ordering = true;
let mut i = 0;
Expand Down Expand Up @@ -318,7 +327,7 @@ impl CLI {
self.game.load_fen(fen);
self.game.clock = Clock::new(1, time * 1000);

let best_move = self.game.root(MAX_PLY).unwrap();
let best_move = self.game.parallel(self.concurrency, self.max_depth).unwrap();
let mut best_move_str = self.game.move_to_san(best_move);

// Add `+` to move in case of check
Expand Down
19 changes: 11 additions & 8 deletions src/protocols/xboard.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::mem;
use std::io;
use regex::Regex;

Expand All @@ -9,12 +8,12 @@ use fen::FEN;
use game::Game;
use moves_generator::MovesGenerator;
use search::Search;
use transpositions::{Transposition, Transpositions};
use version;

pub struct XBoard {
pub game: Game,
max_depth: usize,
concurrency: usize,
force: bool
}

Expand All @@ -23,6 +22,7 @@ impl XBoard {
XBoard {
game: Game::from_fen(DEFAULT_FEN),
max_depth: MAX_PLY - 10,
concurrency: 1,
force: false
}
}
Expand All @@ -45,6 +45,7 @@ impl XBoard {
"ping" => self.cmd_ping(&*args),
"setboard" => self.cmd_setboard(&*args),
"memory" => self.cmd_memory(&*args),
"cores" => self.cmd_cores(&*args),
"sd" => self.cmd_depth(&*args),
"level" => self.cmd_level(&*args),
"protover" => self.cmd_protover(&*args),
Expand Down Expand Up @@ -137,17 +138,19 @@ impl XBoard {
}

pub fn cmd_memory(&mut self, args: &[&str]) {
let s = args[1].parse::<usize>().unwrap(); // In megabytes
let n = (s << 20) / mem::size_of::<Transposition>();

self.game.tt = Transpositions::with_capacity(n);
let memory = args[1].parse::<usize>().unwrap(); // In MB
self.game.tt_resize(memory);
}

pub fn cmd_cores(&mut self, args: &[&str]) {
let cores = args[1].parse::<usize>().unwrap();
self.concurrency = cores;
}

#[allow(unused_variables)] // TODO: remove that
pub fn cmd_protover(&mut self, args: &[&str]) {
println!("feature myname=\"{}\"", version());
println!("feature sigint=0 ping=1 setboard=1 memory=1 done=1");
println!("feature sigint=0 ping=1 setboard=1 memory=1 smp=1 done=1");
// TODO: check that the features got accepted
}

Expand All @@ -167,7 +170,7 @@ impl XBoard {
}

pub fn think(&mut self) {
match self.game.root(self.max_depth) {
match self.game.parallel(self.concurrency, self.max_depth) {
None => {
if self.game.is_check(WHITE) {
println!("0-1 {{black mates}}");
Expand Down
47 changes: 38 additions & 9 deletions src/search.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::cmp;
use std::thread;

use common::*;
use attack::Attack;
Expand All @@ -15,6 +16,7 @@ pub trait Search {
fn quiescence(&mut self, alpha: Score, beta: Score, ply: usize) -> Score;
fn search(&mut self, alpha: Score, beta: Score, depth: usize, ply: usize) -> Score;
fn root(&mut self, max_depth: usize) -> Option<Move>;
fn parallel(&mut self, concurrency: usize, max_depth: usize) -> Option<Move>;
fn print_thinking(&mut self, depth: usize, score: Score, m: Move);
fn get_pv(&mut self, depth: usize) -> String;
}
Expand Down Expand Up @@ -105,7 +107,7 @@ impl Search for Game {
let mut best_move = Move::new_null();

// Try to get the best move from transpositions table
if let Some(t) = self.tt.get(&hash) {
if let Some(t) = self.tt().get(&hash) {
if t.depth() >= depth { // This node has already been searched
match t.bound() {
Bound::Exact => {
Expand Down Expand Up @@ -166,7 +168,7 @@ impl Search for Game {
if iid_allowed && depth > 3 {
self.search(-beta, -alpha, depth / 2, ply + 1);

if let Some(t) = self.tt.get(&hash) {
if let Some(t) = self.tt().get(&hash) {
best_move = t.best_move();
}
}
Expand Down Expand Up @@ -246,7 +248,7 @@ impl Search for Game {
self.moves.add_killer_move(m);
}

self.tt.set(hash, depth, score, m, Bound::Lower);
self.tt().set(hash, depth, score, m, Bound::Lower);
return beta;
}

Expand All @@ -271,7 +273,7 @@ impl Search for Game {
} else {
Bound::Lower
};
self.tt.set(hash, depth, alpha, best_move, bound);
self.tt().set(hash, depth, alpha, best_move, bound);
}

alpha
Expand All @@ -293,7 +295,6 @@ impl Search for Game {
// case we don't decrement the ply counter that is already at 0.
self.moves.clear_all();

self.tt.clear();
self.clock.start(self.positions.len());

let old_fen = self.to_fen();
Expand Down Expand Up @@ -356,7 +357,7 @@ impl Search for Game {
if self.is_verbose && !self.clock.poll(self.nodes_count) {
// TODO: skip the first thousand nodes to gain time?

self.tt.set(hash, depth, score, m, Bound::Exact);
self.tt().set(hash, depth, score, m, Bound::Exact);

// Get the PV line from the TT.
self.print_thinking(depth, score, m);
Expand All @@ -376,7 +377,7 @@ impl Search for Game {
best_score = best_scores[depth];

// TODO: use best_score instead of alpha?
self.tt.set(hash, depth, alpha, best_move, Bound::Exact);
self.tt().set(hash, depth, alpha, best_move, Bound::Exact);
}
if !has_legal_moves {
break;
Expand All @@ -391,7 +392,7 @@ impl Search for Game {
println!("# {:15} {}", "score:", best_score);
println!("# {:15} {} ms", "time:", t);
println!("# {:15} {} ({:.2e} nps)", "nodes:", n, nps);
self.tt.print_stats();
self.tt().print_stats();
}
debug_assert_eq!(old_fen, new_fen);

Expand All @@ -402,6 +403,34 @@ impl Search for Game {
}
}

fn parallel(&mut self, concurrency: usize, max_depth: usize) -> Option<Move> {
if self.is_debug {
println!("# using {} threads", concurrency);
}

self.tt().clear();

let mut children = vec![];
for i in 0..concurrency {
let mut clone = self.clone();
//clone.tt = self.tt.clone();
if i > 0 {
clone.is_verbose = false;
clone.is_debug = false;
}
children.push(thread::spawn(move || {
clone.root(max_depth)
}));
}

let mut results = vec![];
for child in children {
results.push(child.join().unwrap());
}

results[0]
}

fn print_thinking(&mut self, depth: usize, score: Score, m: Move) {
let time = self.clock.elapsed_time() / 10; // In centiseconds

Expand All @@ -427,7 +456,7 @@ impl Search for Game {

let side = self.positions.top().side;
let hash = self.positions.top().hash;
if let Some(t) = self.tt.get(&hash) {
if let Some(t) = self.tt().get(&hash) {
m = t.best_move();

if side == WHITE {
Expand Down
Loading

0 comments on commit 4b2c53e

Please sign in to comment.