Skip to content

Commit

Permalink
Add multithread support (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdmendes authored Apr 1, 2024
2 parents 655eba5 + a43a5ad commit 3c47221
Show file tree
Hide file tree
Showing 16 changed files with 793 additions and 394 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ primitive_enum = "1.2.0"
derive_more = "0.99.17"
rand = "0.8.5"
ctor = "0.2.7"
portable-atomic = "1.6.0"

[profile.dev]
opt-level = 1
Expand Down
3 changes: 3 additions & 0 deletions gauntlet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ if [ ! -f $INSTALL_PATH/${BOOK_NAME} ]; then
cp "${BOOK_PATH}/${BOOK_NAME}" "${INSTALL_PATH}/${BOOK_NAME}" || exit 1
fi

# Delete old binaries
rm $INSTALL_PATH/$ENGINE_NAME-*

# Use binary names based on branch name, with slashes replaced by dashes
CURRENT_BRANCH_BIN_NAME="${ENGINE_NAME}-${CURRENT_BRANCH//\//-}"
UPSTREAM_BIN_NAME="${ENGINE_NAME}-${UPSTREAM//\//-}"
Expand Down
72 changes: 32 additions & 40 deletions src/engine/commands/executor.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
use crate::engine::{time::get_duration, Engine};
use crate::engine::{time::get_duration, Engine, DEFAULT_NUMBER_THREADS, MAX_THREADS};
use camel::{
evaluation::{Evaluable, ValueScore},
evaluation::Evaluable,
moves::gen::{perft, MoveStage},
position::{
fen::{FromFen, ToFen, START_FEN},
Color, Position,
},
search::{
constraint::{HistoryEntry, SearchConstraint, TimeConstraint},
pvs::quiesce,
search_iter,
constraint::{SearchConstraint, TimeConstraint},
history::HistoryEntry,
search_iterative_deepening_multithread,
table::{DEFAULT_TABLE_SIZE_MB, MAX_TABLE_SIZE_MB, MIN_TABLE_SIZE_MB},
Depth, MAX_DEPTH,
},
};
use std::{sync::atomic::Ordering, thread, time::Duration};
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread,
time::Duration,
};

pub fn execute_position(new_position: &Position, game_history: &[Position], engine: &mut Engine) {
engine.position = *new_position;
Expand Down Expand Up @@ -68,23 +75,25 @@ pub fn execute_go(
let stop_now = engine.stop.clone();
let table = engine.table.clone();

let mut constraint = SearchConstraint {
branch_history: engine.game_history.clone(),
let constraint = SearchConstraint {
game_history: engine.game_history.clone(),
time_constraint: calc_move_time
.map(|t| TimeConstraint { initial_instant: std::time::Instant::now(), move_time: t }),
stop_now: Some(stop_now.clone()),
ponder_mode: Some(engine.pondering.clone()),
global_stop: stop_now.clone(),
threads_stop: Arc::new(AtomicBool::new(false)),
ponder_mode: engine.pondering.clone(),
number_threads: engine.number_threads.clone(),
};

thread::spawn(move || {
stop_now.store(false, Ordering::Relaxed);
let current_guess = quiesce(&position, ValueScore::MIN + 1, ValueScore::MAX, &constraint).0;
search_iter(
let current_guess = position.value() * position.side_to_move.sign();
search_iterative_deepening_multithread(
&position,
current_guess,
depth.map_or_else(|| MAX_DEPTH, |d| d as Depth),
table.clone(),
&mut constraint,
&constraint,
);
stop_now.store(true, Ordering::Relaxed);
});
Expand All @@ -109,7 +118,10 @@ pub fn execute_uci() {
println!("id name Camel {}", env!("CARGO_PKG_VERSION"));
println!("id author Bruno Mendes");

// Options list
println!(
"option name Threads type spin default {} min 1 max {}",
DEFAULT_NUMBER_THREADS, MAX_THREADS
);
println!(
"option name Hash type spin default {} min {} max {}",
DEFAULT_TABLE_SIZE_MB, MIN_TABLE_SIZE_MB, MAX_TABLE_SIZE_MB
Expand All @@ -129,7 +141,11 @@ pub fn execute_debug(_: bool) {}
pub fn execute_set_option(name: &str, value: &str, engine: &mut Engine) {
if name == "Hash" {
if let Ok(size) = value.parse::<usize>() {
engine.table.lock().unwrap().set_size(size.clamp(MIN_TABLE_SIZE_MB, MAX_TABLE_SIZE_MB));
engine.table.set_size(size.clamp(MIN_TABLE_SIZE_MB, MAX_TABLE_SIZE_MB));
}
} else if name == "Threads" {
if let Ok(threads) = value.parse::<u16>() {
engine.number_threads.store(threads.clamp(1, MAX_THREADS), Ordering::Relaxed);
}
} else if name == "Ponder" || name == "UCI_Chess960" {
// The time management bonus already takes pondering into account, so do nothing.
Expand All @@ -142,30 +158,7 @@ pub fn execute_set_option(name: &str, value: &str, engine: &mut Engine) {
pub fn execute_uci_new_game(engine: &mut Engine) {
engine.position = Position::from_fen(START_FEN).unwrap();
engine.game_history = Vec::new();
engine.table.lock().unwrap().clear();
}

pub fn execute_auto_move(seconds: u16, engine: &mut Engine) {
println!("The engine is thinking...");

let mut constraint = SearchConstraint {
branch_history: engine.game_history.clone(),
time_constraint: Some(TimeConstraint {
initial_instant: std::time::Instant::now(),
move_time: Duration::from_secs(seconds.into()),
}),
stop_now: None,
ponder_mode: None,
};

search_iter(&engine.position, 0, MAX_DEPTH, engine.table.clone(), &mut constraint);

if let Some(mov) = engine.table.lock().unwrap().get_hash_move(&engine.position) {
engine.position = engine.position.make_move(mov);
execute_display(&engine.position);
} else {
println!("The game is over.");
}
engine.table.clear();
}

pub fn execute_perft(depth: u8, position: &Position) {
Expand Down Expand Up @@ -221,7 +214,6 @@ pub fn execute_help() {
println!("Camel is a UCI-compatible chess engine, primarily meant to be used inside a GUI.");
println!("You can review the UCI standard in https://backscattering.de/chess/uci/.");
println!("Camel also bundles support for custom commands, for debugging purposes:");
println!(" 'automove [seconds]': perform the top engine move on the current board");
println!(" 'perft <depth>': run perft on the current position with the given depth");
println!(" 'move <move>': perform given move in uci notation on the current board");
println!(" 'list': list legal moves available on the current position");
Expand Down
15 changes: 5 additions & 10 deletions src/engine/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ use std::collections::VecDeque;

use self::{
executor::{
execute_all_moves, execute_auto_move, execute_clear, execute_debug, execute_display,
execute_do_move, execute_go, execute_help, execute_is_ready, execute_perft,
execute_ponderhit, execute_position, execute_quit, execute_set_option, execute_stop,
execute_uci, execute_uci_new_game,
},
parser::{
parse_auto_move, parse_debug, parse_go, parse_move, parse_perft, parse_position,
parse_set_option,
execute_all_moves, execute_clear, execute_debug, execute_display, execute_do_move,
execute_go, execute_help, execute_is_ready, execute_perft, execute_ponderhit,
execute_position, execute_quit, execute_set_option, execute_stop, execute_uci,
execute_uci_new_game,
},
parser::{parse_debug, parse_go, parse_move, parse_perft, parse_position, parse_set_option},
};

use super::{Command, Engine};
Expand All @@ -36,7 +33,6 @@ pub fn parse_command(input: &str) -> Result<Command, ()> {
"isready" => Ok(Command::IsReady),
"ucinewgame" => Ok(Command::UCINewGame),
"setoption" => parse_set_option(&mut words),
"automove" | "a" => parse_auto_move(&mut words),
"perft" => parse_perft(&mut words),
"move" | "m" => parse_move(&mut words),
"display" | "d" => Ok(Command::Display),
Expand Down Expand Up @@ -78,7 +74,6 @@ pub fn execute_command(command: Command, engine: &mut Engine) {
}
Command::IsReady => execute_is_ready(),
Command::UCINewGame => execute_uci_new_game(engine),
Command::AutoMove { seconds } => execute_auto_move(seconds, engine),
Command::Perft(depth) => execute_perft(depth, &engine.position),
Command::DoMove { mov_str } => execute_do_move(&mov_str, &mut engine.position),
Command::Display => execute_display(&engine.position),
Expand Down
5 changes: 0 additions & 5 deletions src/engine/commands/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,6 @@ pub fn parse_go(words: &mut VecDeque<&str>) -> Result<Command, String> {
})
}

pub fn parse_auto_move(words: &mut VecDeque<&str>) -> Result<Command, ()> {
let seconds = words.pop_front().unwrap_or("1").parse::<u16>().map_err(|_| ())?;
Ok(Command::AutoMove { seconds })
}

pub fn parse_perft(words: &mut VecDeque<&str>) -> Result<Command, ()> {
let depth = words.pop_front().ok_or(())?.parse::<u8>().map_err(|_| ())?;
Ok(Command::Perft(depth))
Expand Down
19 changes: 12 additions & 7 deletions src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@ use camel::{
Position,
},
search::{
constraint::HistoryEntry,
history::HistoryEntry,
table::{SearchTable, DEFAULT_TABLE_SIZE_MB},
},
};
use std::{
sync::{atomic::AtomicBool, Arc, Mutex},
sync::{
atomic::{AtomicBool, AtomicU16},
Arc,
},
time::Duration,
};

mod commands;
mod time;

pub const DEFAULT_NUMBER_THREADS: u16 = 1;
pub const MAX_THREADS: u16 = 8;

pub enum Command {
// Standard UCI commands
Position {
Expand Down Expand Up @@ -44,9 +50,6 @@ pub enum Command {
},

// Custom commands
AutoMove {
seconds: u16,
},
Perft(u8),
DoMove {
mov_str: String,
Expand All @@ -61,18 +64,20 @@ pub enum Command {
pub struct Engine {
pub position: Position,
pub game_history: Vec<HistoryEntry>,
pub table: Arc<Mutex<SearchTable>>,
pub table: Arc<SearchTable>,
pub stop: Arc<AtomicBool>,
pub pondering: Arc<AtomicBool>,
pub number_threads: Arc<AtomicU16>,
}

pub fn uci_loop() {
let mut engine = Engine {
position: Position::from_fen(START_FEN).unwrap(),
stop: Arc::new(AtomicBool::new(true)),
game_history: Vec::new(),
table: Arc::new(Mutex::new(SearchTable::new(DEFAULT_TABLE_SIZE_MB))),
table: Arc::new(SearchTable::new(DEFAULT_TABLE_SIZE_MB)),
pondering: Arc::new(AtomicBool::new(false)),
number_threads: Arc::new(AtomicU16::new(DEFAULT_NUMBER_THREADS)),
};

println!("Camel {} by Bruno Mendes", env!("CARGO_PKG_VERSION"));
Expand Down
33 changes: 9 additions & 24 deletions src/moves/attacks/magics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ use super::sliders::{slider_attacks_from_square, BISHOP_MOVE_DIRECTIONS, ROOK_MO
use crate::position::{bitboard::Bitboard, board::Piece, square::Square};
use ctor::ctor;
use rand::{rngs::StdRng, Rng, SeedableRng};
use std::{
array,
sync::{Arc, Mutex},
thread,
};
use std::thread;

#[ctor]
pub static ROOK_MAGICS: [SquareMagic; 64] = find_magics(Piece::Rook);
Expand Down Expand Up @@ -100,25 +96,14 @@ fn find_magic(square: Square, piece: Piece) -> SquareMagic {
}

fn find_magics(piece: Piece) -> [SquareMagic; 64] {
let magics: [SquareMagic; 64] = array::from_fn(|_| SquareMagic::default());
let magics = Arc::new(Mutex::new(magics));

let handles = (0..64)
.map(|square| {
let magics = Arc::clone(&magics);
thread::spawn(move || {
let magic = find_magic(Square::from(square).unwrap(), piece);
let mut magics = magics.lock().unwrap();
magics[square as usize] = magic;
})
})
.collect::<Vec<_>>();

for handle in handles {
handle.join().unwrap();
}

Arc::try_unwrap(magics).unwrap().into_inner().unwrap()
(0..64)
.map(|square| thread::spawn(move || find_magic(Square::from(square).unwrap(), piece)))
.collect::<Vec<_>>()
.into_iter()
.map(|h| h.join().unwrap())
.collect::<Vec<_>>()
.try_into()
.unwrap()
}

pub fn magic_index(magic: &SquareMagic, occupancy: Bitboard) -> usize {
Expand Down
8 changes: 8 additions & 0 deletions src/moves/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ impl MoveFlag {
pub struct Move(u16);

impl Move {
pub fn new_raw(bytes: u16) -> Self {
Move(bytes)
}

pub fn new(from: Square, to: Square, flag: MoveFlag) -> Self {
Move((from as u16) | ((to as u16) << 6) | ((flag as u16) << 12))
}
Expand All @@ -100,6 +104,10 @@ impl Move {
_ => None,
}
}

pub fn raw(&self) -> u16 {
self.0
}
}

impl std::fmt::Display for Move {
Expand Down
Loading

0 comments on commit 3c47221

Please sign in to comment.