From 060ceaed5f5869bd7978fdf7c785a72fecbc63b3 Mon Sep 17 00:00:00 2001 From: Stephen Fleischman Date: Mon, 26 Mar 2018 15:30:27 -0700 Subject: [PATCH 1/7] Added prefetching to pawntable, material table, and allowed aplpying a move to prefetch entries to these tables with the `PreFetchable` trait. Signed-off-by: stephenf --- pleco/src/board/mod.rs | 36 +++++++++++--- pleco/src/tools/mod.rs | 12 +++++ pleco/src/tools/tt.rs | 16 ++++--- pleco_engine/Cargo.toml | 1 + pleco_engine/src/consts.rs | 2 + pleco_engine/src/lib.rs | 1 + pleco_engine/src/search/mod.rs | 36 +++++++------- pleco_engine/src/tables/material.rs | 15 ++++++ pleco_engine/src/tables/pawn_table.rs | 60 +++++++++++++++++------- pleco_engine/src/time/time_management.rs | 8 ++-- 10 files changed, 135 insertions(+), 52 deletions(-) diff --git a/pleco/src/board/mod.rs b/pleco/src/board/mod.rs index 4bb849a..df492cf 100644 --- a/pleco/src/board/mod.rs +++ b/pleco/src/board/mod.rs @@ -27,7 +27,7 @@ use tools::pleco_arc::{Arc,UniqueArc}; use helper::Helper; use helper::prelude::*; use tools::prng::PRNG; -use tools::Searcher; +use tools::{Searcher,PreFetchable}; use bot_prelude::AlphaBetaSearcher; use self::castle_rights::Castling; @@ -86,6 +86,14 @@ impl fmt::Debug for FenBuildError { } } +struct PreFetchDummy { + +} + +impl PreFetchable for PreFetchDummy { + fn prefetch(&self, _key: u64) {} +} + /// Represents a Chessboard through a `Board`. /// @@ -544,16 +552,23 @@ impl Board { /// `Board::generate_moves()`, which guarantees that only Legal moves will be created. pub fn apply_move(&mut self, bit_move: BitMove) { let gives_check: bool = self.gives_check(bit_move); - self.apply_unknown_move(bit_move, gives_check); + let pt_d = PreFetchDummy {}; + let mt_d = PreFetchDummy {}; + self.apply_move_pft_chk::(bit_move, gives_check, &pt_d, &mt_d); } /// Applies a move to the Board. This method is only useful if before a move is applied to /// a board, the ability of the move to give check is applied. If it is not needed to know /// if the move gives check or not, consider using `Board::apply_move` instead. /// + /// This method also takes in two generic parameters implementing `PreFetchable`, one of which + /// will prefetch from the A table taking in a pawn key, the other of which pre-fetching + /// from a table utilizing the material key. + /// /// # Safety /// - /// The passed in `BitMove` must be a legal move for the current position. + /// The passed in `BitMove` must be a legal move for the current position, and the gives_check + /// parameter must be correct for the move. /// /// # Panics /// @@ -564,7 +579,9 @@ impl Board { /// The second parameter, `gives_check`, must be true if the move gives check, or false /// if the move doesn't give check. If an incorrect `gives_check` is supplied, undefined /// behavior will follow. - pub fn apply_unknown_move(&mut self, bit_move: BitMove, gives_check: bool) { + pub fn apply_move_pft_chk(&mut self, bit_move: BitMove, gives_check: bool, + pawn_table: &PT, material_table: &MT) + where PT: PreFetchable, MT: PreFetchable { // Check for stupidity assert_ne!(bit_move.get_src(), bit_move.get_dest()); @@ -578,9 +595,9 @@ impl Board { // New Arc for the board to have by making a partial clone of the current state let mut next_arc_state = UniqueArc::new(self.state.partial_clone()); + // Seperate Block to allow derefencing the BoardState + // As there is garunteed only one owner of the Arc, this is allowed { - // Seperate Block to allow derefencing the BoardState - // As there is garunteed only one owner of the Arc, this is allowed let new_state: &mut BoardState = &mut *next_arc_state; // Set the prev state @@ -651,9 +668,13 @@ impl Board { self.remove_piece_c(captured, cap_sq); } zob ^= z_square(cap_sq, captured); + + // update material key and prefetch access to a Material Table let cap_count = self.count_piece(them, captured.type_of()); material_key ^= z_square(SQ(cap_count), captured); + material_table.prefetch(material_key); new_state.psq -= psq(captured, cap_sq); + // Reset Rule 50 new_state.rule_50 = 0; new_state.captured_piece = captured.type_of(); @@ -709,7 +730,10 @@ impl Board { new_state.psq += psq(us_promo,to) - psq(piece, to); new_state.nonpawn_material[us as usize] += piece_value(us_promo, false); } + + // update pawn key and prefetch access pawn_key ^= z_square(from, piece) ^ z_square(to, piece); + pawn_table.prefetch2(pawn_key); new_state.rule_50 = 0; } diff --git a/pleco/src/tools/mod.rs b/pleco/src/tools/mod.rs index cdcc82f..fa178c0 100644 --- a/pleco/src/tools/mod.rs +++ b/pleco/src/tools/mod.rs @@ -21,3 +21,15 @@ pub trait Searcher { where Self: Sized; } + +/// Allows an object to have it's entries pre-fetchable. +pub trait PreFetchable { + /// Pre-fetches a particular key. This means bringing it into the cache for faster access. + fn prefetch(&self, key: u64); + + /// Pre-fetches a particular key, alongside the next key. + fn prefetch2(&self, key: u64) { + self.prefetch(key); + self.prefetch(key + 1); + } +} diff --git a/pleco/src/tools/tt.rs b/pleco/src/tools/tt.rs index b59a609..36c7cb2 100644 --- a/pleco/src/tools/tt.rs +++ b/pleco/src/tools/tt.rs @@ -41,6 +41,7 @@ use std::cell::UnsafeCell; use prefetch::prefetch::*; +use super::PreFetchable; use core::piece_move::BitMove; // TODO: investigate potention for SIMD in key lookup @@ -447,22 +448,23 @@ impl TranspositionTable { (hits * 100.0) / (clusters_scanned * CLUSTER_SIZE as u64) as f64 } } +} + +unsafe impl Sync for TranspositionTable {} +impl PreFetchable for TranspositionTable { /// Pre-fetches a particular key. This means bringing it into the cache for faster eventual /// access. #[inline(always)] - pub fn prefetch(&self, key: u64) { + fn prefetch(&self, key: u64) { let index: usize = ((self.num_clusters() - 1) as u64 & key) as usize; - unsafe { - let ptr = (*self.clusters.get()).as_ptr().offset(index as isize); - prefetch::(ptr); + unsafe { + let ptr = (*self.clusters.get()).as_ptr().offset(index as isize); + prefetch::(ptr); }; } } -unsafe impl Sync for TranspositionTable {} - - impl Drop for TranspositionTable { fn drop(&mut self) { unsafe {self.de_alloc();} diff --git a/pleco_engine/Cargo.toml b/pleco_engine/Cargo.toml index b6c6d29..69da428 100644 --- a/pleco_engine/Cargo.toml +++ b/pleco_engine/Cargo.toml @@ -73,6 +73,7 @@ chrono = "0.4.1" rand = "0.4.2" rayon = "1.0.1" num_cpus = "1.8.0" +prefetch = "0.2.0" crossbeam-utils = "0.3.2" [dependencies.lazy_static] diff --git a/pleco_engine/src/consts.rs b/pleco_engine/src/consts.rs index 377f030..bed36b9 100644 --- a/pleco_engine/src/consts.rs +++ b/pleco_engine/src/consts.rs @@ -12,6 +12,7 @@ use pleco::helper::prelude; use threadpool; use search; +use tables::pawn_table; pub const MAX_PLY: u16 = 126; pub const THREAD_STACK_SIZE: usize = MAX_PLY as usize + 7; @@ -37,6 +38,7 @@ pub fn init_globals() { prelude::init_statics(); // Initialize static tables compiler_fence(Ordering::SeqCst); lazy_static::initialize(&TT_TABLE); // Transposition Table + pawn_table::init(); threadpool::init_threadpool(); // Make Threadpool search::init(); }); diff --git a/pleco_engine/src/lib.rs b/pleco_engine/src/lib.rs index 44a27cc..9ab704c 100644 --- a/pleco_engine/src/lib.rs +++ b/pleco_engine/src/lib.rs @@ -44,6 +44,7 @@ extern crate rand; extern crate pleco; extern crate chrono; extern crate crossbeam_utils; +extern crate prefetch; pub mod threadpool; pub mod sync; diff --git a/pleco_engine/src/search/mod.rs b/pleco_engine/src/search/mod.rs index d35d6d8..3161ec4 100644 --- a/pleco_engine/src/search/mod.rs +++ b/pleco_engine/src/search/mod.rs @@ -13,6 +13,7 @@ use pleco::core::*; use pleco::tools::tt::*; use pleco::core::score::*; use pleco::tools::pleco_arc::Arc; +use pleco::tools::PreFetchable; use pleco::helper::prelude::*; use pleco::core::piece_move::MoveType; //use pleco::board::movegen::{MoveGen,PseudoLegal}; @@ -300,8 +301,6 @@ impl Searcher { // The per thread searching function fn search_root(&mut self) { - - // Early return. This shouldn't notmally happen. if self.stop() { return; @@ -444,15 +443,14 @@ impl Searcher { // Main thread only from here on! - // check for time if let Some(_) = self.limit.use_time_management() { if !self.stop() { let score_diff: i32 = best_value - self.previous_score; - let improving_factor: i64 = (246).max((750).min( - 353 - + 109 * self.failed_low as i64 + let improving_factor: i64 = (246).max((832).min( + 306 + + 119 * self.failed_low as i64 - 6 * score_diff as i64)); time_reduction = 1.0; @@ -460,17 +458,17 @@ impl Searcher { // If the bestMove is stable over several iterations, reduce time accordingly for i in 3..6 { if self.last_best_move_depth * i < self.depth_completed { - time_reduction *= 1.43; + time_reduction *= 1.40; } } // Use part of the gained time from a previous stable move for the current move let mut unstable_factor: f64 = 1.0 + self.best_move_changes; - unstable_factor *= self.previous_time_reduction.powf(0.42) / time_reduction; + unstable_factor *= self.previous_time_reduction.powf(0.46) / time_reduction; // Stop the search if we have only one legal move, or if available time elapsed if self.root_moves().len() == 1 - || TIMER.elapsed() >= (TIMER.ideal_time() as f64 * unstable_factor as f64 * improving_factor as f64 / 609.0) as i64 { + || TIMER.elapsed() >= (TIMER.ideal_time() as f64 * unstable_factor as f64 * improving_factor as f64 / 600.0) as i64 { threadpool().set_stop(true); break 'iterative_deepening; } @@ -663,7 +661,7 @@ impl Searcher { // Futility Pruning. Disregard moves that have little chance of raising the callee's // alpha value. Rather, return the position evaluation as an estimate for the current - // move's strenth + // move's strength if !at_root && depth < 7 && pos_eval - futility_margin(depth, improving) >= beta @@ -785,6 +783,9 @@ impl Searcher { } } + // speculative prefetch for the next key. + self.tt.prefetch(self.board.key_after(mov)); + if !self.board.legal_move(mov) { ss.move_count -= 1; moves_played -= 1; @@ -800,7 +801,7 @@ impl Searcher { // do the move self.apply_move(mov, gives_check); - // prefetch the zobrist key + // prefetch next TT entry self.tt.prefetch(self.board.zobrist()); // At higher depths, do a search of a lower ply to see if this move is @@ -1125,6 +1126,8 @@ impl Searcher { continue; } + self.tt.prefetch(self.board.key_after(mov)); + if !self.board.legal_move(mov) { moves_played -= 1; continue; @@ -1132,7 +1135,10 @@ impl Searcher { ss.current_move = mov; self.apply_move(mov, gives_check); + + // prefetch next TT entry self.tt.prefetch(self.board.zobrist()); + assert_eq!(gives_check, self.board.in_check()); value = -self.qsearch::(-beta, -alpha, ss.incr(),rev_depth - 1); @@ -1176,10 +1182,6 @@ impl Searcher { ss.static_eval as i16, tt_depth, node_bound, self.tt.time_age()); - - if best_value <= NEG_INFINITE { - println!("b : {}, ply: {}", best_value, ply); - } assert!(best_value > NEG_INFINITE); assert!(best_value < INFINITE ); best_value @@ -1249,7 +1251,7 @@ impl Searcher { #[inline(always)] fn apply_move(&mut self, mov: BitMove, gives_check: bool) { self.nodes.fetch_add(1, Ordering::Relaxed); - self.board.apply_unknown_move(mov, gives_check); + self.board.apply_move_pft_chk(mov, gives_check, &self.pawns, &self.material); } pub fn eval(&mut self) -> Value { @@ -1321,8 +1323,8 @@ impl Searcher { s.push_str(" upperbound"); } s.push_str(&format!(" nodes {}", nodes)); - s.push_str(&format!(" nps {}", (nodes * 1000) / elapsed)); if elapsed > 1000 { + s.push_str(&format!(" nps {}", (nodes * 1000) / elapsed)); s.push_str(&format!(" hashfull {:.2}", self.tt.hash_percent())); } s.push_str(&format!(" time {}", elapsed)); diff --git a/pleco_engine/src/tables/material.rs b/pleco_engine/src/tables/material.rs index b7303cf..6cd54d3 100644 --- a/pleco_engine/src/tables/material.rs +++ b/pleco_engine/src/tables/material.rs @@ -1,9 +1,12 @@ //! Table to map from position -> material value; +use prefetch::prefetch::*; + use pleco::{Player, Board, PieceType}; use pleco::core::masks::{PLAYER_CNT,PIECE_TYPE_CNT}; use pleco::core::score::*; use pleco::core::mono_traits::*; +use pleco::tools::PreFetchable; use super::TableBase; @@ -62,6 +65,18 @@ pub struct Material { table: TableBase, } +impl PreFetchable for Material { + /// Pre-fetches a particular key. This means bringing it into the cache for faster eventual + /// access. + #[inline(always)] + fn prefetch(&self, key: u64) { + unsafe { + let ptr = self.table.get_ptr(key); + prefetch::(ptr); + } + } +} + unsafe impl Send for Material {} impl Material { diff --git a/pleco_engine/src/tables/pawn_table.rs b/pleco_engine/src/tables/pawn_table.rs index ecac2fa..bcbdf7b 100644 --- a/pleco_engine/src/tables/pawn_table.rs +++ b/pleco_engine/src/tables/pawn_table.rs @@ -4,6 +4,10 @@ //! An entry is retrieved from the `pawn_key` field of a `Board`. A key is not garunteed to be //! unique to a pawn structure, but it's very likely that there will be no collisions. +use std::mem::transmute; + +use prefetch::prefetch::*; + use pleco::{Player, File, SQ, BitBoard, Board, PieceType, Rank, Piece}; use pleco::core::masks::{PLAYER_CNT,RANK_CNT}; use pleco::core::score::*; @@ -11,11 +15,10 @@ use pleco::core::mono_traits::*; use pleco::board::castle_rights::Castling; use pleco::core::CastleType; use pleco::helper::prelude::*; +use pleco::tools::PreFetchable; use super::TableBase; -use std::mem::transmute; - // isolated pawn penalty @@ -77,11 +80,13 @@ const STORM_DANGER: [[[Value; RANK_CNT]; 4]; 4] = [ [ 21, 23, 116, 41, 15, 0, 0, 0 ] ] ]; -//[[[[Score; 2]; 2] ;3]; RANK_CNT] = -lazy_static!{ - static ref CONNECTED: [[[[Score; RANK_CNT]; 3] ;2]; 2] = { + +pub static mut CONNECTED: [[[[Score; RANK_CNT]; 3] ;2]; 2] = [[[[Score(0,0); RANK_CNT]; 3] ;2]; 2]; + +/// Initalizes the CONNECTED table. +pub fn init() { + unsafe { let seed: [i32; 8] = [0, 13, 24, 18, 76, 100, 175, 330]; - let mut a: [[[[Score; RANK_CNT]; 3] ;2]; 2] = [[[[Score(0,0); RANK_CNT]; 3] ;2]; 2]; for opposed in 0..2 { for phalanx in 0..2 { for support in 0..3 { @@ -89,14 +94,12 @@ lazy_static!{ let mut v: i32 = 17 * support; v += (seed[r] + (phalanx * ((seed[r as usize +1] - seed[r as usize]) / 2))) >> opposed; let eg: i32 = v * (r as i32 - 2) / 4; -// a[r as usize][support as usize][phalanx as usize][opposed as usize] = Score(v, eg); - a[opposed as usize][phalanx as usize][support as usize][r as usize] = Score(v, eg); + CONNECTED[opposed as usize][phalanx as usize][support as usize][r as usize] = Score(v, eg); } } } } - a - }; + } } fn init_connected() -> [[[[Score; 2]; 2] ;3]; RANK_CNT] { @@ -175,6 +178,28 @@ impl PawnTable { } } +impl PreFetchable for PawnTable { + /// Pre-fetches a particular key. This means bringing it into the cache for faster eventual + /// access. + #[inline(always)] + fn prefetch(&self, key: u64) { + unsafe { + let ptr = self.table.get_ptr(key); + prefetch::(ptr); + } + } + + #[inline(always)] + fn prefetch2(&self, key: u64) { + unsafe { + let ptr = self.table.get_ptr(key); + prefetch::(ptr); + let ptr_2 = (ptr as *mut u8).offset(64) as *mut PawnEntry; + prefetch::(ptr_2); + } + } +} + /// Information on a the pawn structure for a given position. /// /// This information is computed upon access. @@ -439,14 +464,12 @@ impl PawnEntry { } if supported.is_not_empty() | supported.is_not_empty() { - score += CONNECTED[opposed as usize] - [phalanx.is_not_empty() as usize] - [supported.count_bits() as usize] - [P::player().relative_rank_of_sq(s) as usize]; -// score += CONNECTED[P::player().relative_rank_of_sq(s) as usize] -// [supported.count_bits() as usize] -// [phalanx.is_not_empty() as usize] -// [opposed as usize]; + score += unsafe { + CONNECTED[opposed as usize] + [phalanx.is_not_empty() as usize] + [supported.count_bits() as usize] + [P::player().relative_rank_of_sq(s) as usize] + }; } else if neighbours.is_empty() { score -= ISOLATED; self.weak_unopposed[P::player() as usize] += (!opposed) as i16; @@ -463,6 +486,7 @@ impl PawnEntry { } } + #[cfg(test)] mod tests { use super::*; diff --git a/pleco_engine/src/time/time_management.rs b/pleco_engine/src/time/time_management.rs index 0964f7e..1da0154 100644 --- a/pleco_engine/src/time/time_management.rs +++ b/pleco_engine/src/time/time_management.rs @@ -11,13 +11,13 @@ use std::f64; const MOVE_HORIZON: i64 = 50; -const MAX_RATIO: f64 = 5.10; -const STEAL_RATIO: f64 = 0.32; +const MAX_RATIO: f64 = 6.49; +const STEAL_RATIO: f64 = 0.34; // TODO: These should be made into UCIOptions const MIN_THINKING_TIME: i64 = 20; -const MOVE_OVERHEAD: i64 = 105; -const SLOW_MOVER: i64 = 30; +const MOVE_OVERHEAD: i64 = 100; +const SLOW_MOVER: i64 = 45; #[derive(PartialEq)] enum TimeCalc { From b5e29bcd889a977d4c2761b14b2c9461d63f0a3b Mon Sep 17 00:00:00 2001 From: Stephen Fleischman Date: Mon, 26 Mar 2018 15:30:27 -0700 Subject: [PATCH 2/7] Removed dependency on lazy_static, rather initialize both the TT and the TimeManager on startup. Signed-off-by: stephenf --- pleco_engine/Cargo.toml | 9 +--- pleco_engine/benches/multimove_benches.rs | 13 +++--- pleco_engine/benches/startpos_benches.rs | 7 ++- pleco_engine/src/consts.rs | 55 ++++++++++++++++++++--- pleco_engine/src/engine.rs | 7 ++- pleco_engine/src/lib.rs | 3 -- pleco_engine/src/search/mod.rs | 24 +++++----- pleco_engine/src/threadpool/mod.rs | 12 ++--- 8 files changed, 79 insertions(+), 51 deletions(-) diff --git a/pleco_engine/Cargo.toml b/pleco_engine/Cargo.toml index 69da428..98c64fe 100644 --- a/pleco_engine/Cargo.toml +++ b/pleco_engine/Cargo.toml @@ -71,16 +71,10 @@ pleco = { path = "../pleco", version = "0.4.0" } clippy = {version = "0.0.191", optional = true} chrono = "0.4.1" rand = "0.4.2" -rayon = "1.0.1" num_cpus = "1.8.0" prefetch = "0.2.0" crossbeam-utils = "0.3.2" -[dependencies.lazy_static] -version = "1.0.0" -features = ["nightly"] - - [features] default = [] dev = ["clippy"] @@ -92,7 +86,8 @@ test = false doc = false [dev-dependencies] -criterion = { version = '0.2.2', default-features = false, features=['real_blackbox'] } +criterion = { version = '0.2.2', default-features = false, features=['real_blackbox'] } +lazy_static = {version = "1.0.0", features = ["nightly"]} [[bench]] name = "bench_engine_main" diff --git a/pleco_engine/benches/multimove_benches.rs b/pleco_engine/benches/multimove_benches.rs index ff93370..d7b755e 100644 --- a/pleco_engine/benches/multimove_benches.rs +++ b/pleco_engine/benches/multimove_benches.rs @@ -5,7 +5,6 @@ use pleco::{Board}; use pleco_engine::engine::PlecoSearcher; use pleco_engine::time::uci_timer::PreLimits; -use pleco_engine::consts::*; use pleco_engine::threadpool::*; use super::*; @@ -15,12 +14,12 @@ const KIWIPETE: &str = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R fn search_kiwipete_3moves_engine(b: &mut Bencher) { let mut pre_limit = PreLimits::blank(); pre_limit.depth = Some(D::depth()); - let _searcher = PlecoSearcher::init(false); + let mut searcher = PlecoSearcher::init(false); let limit = pre_limit.create(); let board_kwi: Board = Board::from_fen(KIWIPETE).unwrap(); b.iter_with_setup(|| { threadpool().clear_all(); - unsafe {TT_TABLE.clear() }; + searcher.clear_tt(); board_kwi.shallow_clone() }, |mut board| { let mov = black_box(threadpool().search(&board, &limit)); @@ -34,11 +33,11 @@ fn search_kiwipete_3moves_engine(b: &mut Bencher) { fn search_startpos_3moves_engine(b: &mut Bencher) { let mut pre_limit = PreLimits::blank(); pre_limit.depth = Some(D::depth()); - let _searcher = PlecoSearcher::init(false); + let mut searcher = PlecoSearcher::init(false); let limit = pre_limit.create(); b.iter_with_setup(|| { threadpool().clear_all(); - unsafe {TT_TABLE.clear() }; + searcher.clear_tt(); Board::start_pos() }, |mut board| { let mov = black_box(threadpool().search(&board, &limit)); @@ -62,8 +61,8 @@ fn bench_engine_evaluations(c: &mut Criterion) { criterion_group!(name = search_multimove; config = Criterion::default() - .sample_size(18) - .warm_up_time(Duration::from_millis(200)); + .sample_size(22) + .warm_up_time(Duration::from_millis(100)); targets = bench_engine_evaluations ); diff --git a/pleco_engine/benches/startpos_benches.rs b/pleco_engine/benches/startpos_benches.rs index 4db6f23..0e0d454 100644 --- a/pleco_engine/benches/startpos_benches.rs +++ b/pleco_engine/benches/startpos_benches.rs @@ -5,7 +5,6 @@ use pleco::{Board}; use pleco_engine::engine::PlecoSearcher; use pleco_engine::time::uci_timer::PreLimits; -use pleco_engine::consts::*; use pleco_engine::threadpool::*; use super::*; @@ -14,11 +13,11 @@ use super::*; fn search_singular_engine(b: &mut Bencher) { let mut pre_limit = PreLimits::blank(); pre_limit.depth = Some(D::depth()); - let _searcher = PlecoSearcher::init(false); + let mut searcher = PlecoSearcher::init(false); let limit = pre_limit.create(); b.iter_with_setup(|| { threadpool().clear_all(); - unsafe {TT_TABLE.clear() }; + searcher.clear_tt(); Board::start_pos() }, |board| { black_box(threadpool().search(&board, &limit)); @@ -35,7 +34,7 @@ fn bench_engine_evaluations(c: &mut Criterion) { criterion_group!(name = search_singular; config = Criterion::default() - .sample_size(23) + .sample_size(25) .warm_up_time(Duration::from_millis(100)); targets = bench_engine_evaluations ); diff --git a/pleco_engine/src/consts.rs b/pleco_engine/src/consts.rs index bed36b9..ffa51a6 100644 --- a/pleco_engine/src/consts.rs +++ b/pleco_engine/src/consts.rs @@ -1,6 +1,7 @@ //! Constant values and static structures. -use lazy_static; +use std::heap::{Alloc, Layout, Heap}; +use std::ptr::{NonNull, self}; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::sync::{ONCE_INIT,Once}; @@ -9,7 +10,7 @@ use std::sync::atomic::compiler_fence; use pleco::tools::tt::TranspositionTable; use pleco::helper::prelude; - +use time::time_management::TimeManager; use threadpool; use search; use tables::pawn_table; @@ -29,21 +30,63 @@ pub static USE_STDOUT: AtomicBool = AtomicBool::new(true); /// Global Timer //pub static TIMER: TimeManager = TimeManager::uninitialized(); -lazy_static! { - pub static ref TT_TABLE: TranspositionTable = TranspositionTable::new(DEFAULT_TT_SIZE); -} +static mut TT_TABLE: NonNull = unsafe + {NonNull::new_unchecked(ptr::null_mut())}; + +static mut TIMER: NonNull = unsafe + {NonNull::new_unchecked(ptr::null_mut())}; pub fn init_globals() { INITALIZED.call_once(|| { prelude::init_statics(); // Initialize static tables compiler_fence(Ordering::SeqCst); - lazy_static::initialize(&TT_TABLE); // Transposition Table + init_tt(); // Transposition Table + init_timer(); // Global timer manager pawn_table::init(); threadpool::init_threadpool(); // Make Threadpool search::init(); }); } +// initalizes the transposition table +fn init_tt() { + unsafe { + let layout = Layout::new::(); + let result = Heap.alloc_zeroed(layout); + let new_ptr: *mut TranspositionTable = match result { + Ok(ptr) => ptr as *mut TranspositionTable, + Err(err) => Heap.oom(err), + }; + ptr::write(new_ptr, TranspositionTable::new(DEFAULT_TT_SIZE)); + TT_TABLE = NonNull::new_unchecked(new_ptr); + } +} + +fn init_timer() { + unsafe { + let layout = Layout::new::(); + let result = Heap.alloc_zeroed(layout); + let new_ptr: *mut TimeManager = match result { + Ok(ptr) => ptr as *mut TimeManager, + Err(err) => Heap.oom(err), + }; + ptr::write(new_ptr, TimeManager::uninitialized()); + TIMER = NonNull::new_unchecked(new_ptr); + } +} + +pub fn timer() -> &'static TimeManager { + unsafe { + &*TIMER.as_ptr() + } +} + +/// Returns access to the global transposition table +pub fn tt() -> &'static TranspositionTable { + unsafe { + &*TT_TABLE.as_ptr() + } +} pub trait PVNode { diff --git a/pleco_engine/src/engine.rs b/pleco_engine/src/engine.rs index 83a6683..c127d7a 100644 --- a/pleco_engine/src/engine.rs +++ b/pleco_engine/src/engine.rs @@ -9,7 +9,6 @@ use pleco::BitMove; use time::uci_timer::{PreLimits}; use uci::options::{OptionsMap,OptionWork}; use uci::parse; -use TT_TABLE; use consts::*; use threadpool::threadpool; @@ -207,15 +206,15 @@ impl PlecoSearcher { } pub fn hash_percent(&self) -> f64 { - TT_TABLE.hash_percent() + tt().hash_percent() } pub fn clear_tt(&mut self) { - unsafe {TT_TABLE.clear() }; + unsafe {tt().clear() }; } pub fn resize_tt(&mut self, mb: usize) { - unsafe {TT_TABLE.resize_to_megabytes(mb)}; + unsafe {tt().resize_to_megabytes(mb)}; } pub fn use_stdout(&mut self, stdout: bool) { diff --git a/pleco_engine/src/lib.rs b/pleco_engine/src/lib.rs index 9ab704c..6c4cb05 100644 --- a/pleco_engine/src/lib.rs +++ b/pleco_engine/src/lib.rs @@ -36,9 +36,6 @@ //#![crate_type = "staticlib"] -#[macro_use] -extern crate lazy_static; - extern crate num_cpus; extern crate rand; extern crate pleco; diff --git a/pleco_engine/src/search/mod.rs b/pleco_engine/src/search/mod.rs index 3161ec4..a9bccf4 100644 --- a/pleco_engine/src/search/mod.rs +++ b/pleco_engine/src/search/mod.rs @@ -19,12 +19,11 @@ use pleco::core::piece_move::MoveType; //use pleco::board::movegen::{MoveGen,PseudoLegal}; //use pleco::core::mono_traits::{QuietChecksGenType}; -use {MAX_PLY,TT_TABLE,THREAD_STACK_SIZE}; +use {MAX_PLY,THREAD_STACK_SIZE}; use threadpool::threadpool; use time::time_management::TimeManager; use time::uci_timer::*; -use threadpool::TIMER; use sync::{GuardedBool,LockLatch}; use root_moves::RootMove; use root_moves::root_moves_list::RootMoveList; @@ -187,8 +186,8 @@ impl Searcher { depth_completed: 0, limit: Limits::blank(), board: Board::start_pos(), - time_man: &TIMER, - tt: &TT_TABLE, + time_man: timer(), + tt: tt(), pawns: PawnTable::new(16384), material: Material::new(8192), root_moves: UnsafeCell::new(RootMoveList::new()), @@ -389,7 +388,7 @@ impl Searcher { if self.use_stdout() && self.main_thread() && (best_value <= alpha || best_value >= beta) - && TIMER.elapsed() > 3000 { + && self.time_man.elapsed() > 3000 { println!("{}",self.pv(depth, alpha, beta)); } @@ -414,7 +413,7 @@ impl Searcher { } // Main Thread provides an update to the GUI - if self.use_stdout() && self.main_thread() && TIMER.elapsed() > 6 { + if self.use_stdout() && self.main_thread() && self.time_man.elapsed() > 6 { if self.stop() { println!("{}",self.pv(depth, NEG_INFINITE, INFINITE)); } else { @@ -468,7 +467,10 @@ impl Searcher { // Stop the search if we have only one legal move, or if available time elapsed if self.root_moves().len() == 1 - || TIMER.elapsed() >= (TIMER.ideal_time() as f64 * unstable_factor as f64 * improving_factor as f64 / 600.0) as i64 { + || self.time_man.elapsed() >= + (self.time_man.ideal_time() as f64 + * unstable_factor as f64 + * improving_factor as f64 / 600.0) as i64 { threadpool().set_stop(true); break 'iterative_deepening; } @@ -570,7 +572,7 @@ impl Searcher { // probe the transposition table excluded_move = ss.excluded_move; zob = self.board.zobrist() ^ (excluded_move.get_raw() as u64).wrapping_shl(16); - let (tt_hit, tt_entry): (bool, &mut Entry) = TT_TABLE.probe(zob); + let (tt_hit, tt_entry): (bool, &mut Entry) = self.tt.probe(zob); let tt_value: Value = if tt_hit {tt_entry.score as i32} else {NONE}; let tt_move: BitMove = if at_root {self.root_moves().first().bit_move} else if tt_hit {tt_entry.best_move} else {BitMove::null()}; @@ -1005,7 +1007,7 @@ impl Searcher { let ply: u16 = ss.ply; let zob: u64 = self.board.zobrist(); - let (tt_hit, tt_entry): (bool, &mut Entry) = TT_TABLE.probe(zob); + let (tt_hit, tt_entry): (bool, &mut Entry) = self.tt.probe(zob); let tt_value: Value = if tt_hit {tt_entry.score as i32} else {NONE}; let mut value: Value; @@ -1272,7 +1274,7 @@ impl Searcher { fn check_time(&mut self) { if self.limit.use_time_management().is_some() - && TIMER.elapsed() >= TIMER.maximum_time() { + && self.time_man.elapsed() >= self.time_man.maximum_time() { threadpool().set_stop(true); } else if let Some(time) = self.limit.use_movetime() { if self.limit.elapsed() >= time as i64 { @@ -1303,7 +1305,7 @@ impl Searcher { /// Useful information to tell to the GUI fn pv(&self, depth: i16, alpha: i32, beta: i32) -> String { let root_move: &RootMove= self.root_moves().first(); - let elapsed = TIMER.elapsed() as u64; + let elapsed = self.time_man.elapsed() as u64; let nodes = threadpool().nodes(); let mut s = String::from("info"); let mut score = if root_move.score == NEG_INFINITE { diff --git a/pleco_engine/src/threadpool/mod.rs b/pleco_engine/src/threadpool/mod.rs index b77e4bb..bd2adc2 100644 --- a/pleco_engine/src/threadpool/mod.rs +++ b/pleco_engine/src/threadpool/mod.rs @@ -17,7 +17,6 @@ use pleco::core::piece_move::BitMove; use sync::LockLatch; use time::uci_timer::*; -use time::time_management::TimeManager; use search::Searcher; use consts::*; @@ -60,11 +59,6 @@ pub fn threadpool() -> &'static mut ThreadPool { } } -/// Global Timer -lazy_static! { - pub static ref TIMER: TimeManager = TimeManager::uninitialized(); -} - // Dummy struct to allow us to pass a pointer into a spawned thread. struct SearcherPtr { ptr: UnsafeCell<*mut Searcher> @@ -295,10 +289,10 @@ impl ThreadPool { pub fn uci_search(&mut self, board: &Board, limits: &Limits) { // Start the timer! - if let Some(timer) = limits.use_time_management() { - TIMER.init(limits.start, &timer, board.turn(), board.moves_played()); + if let Some(uci_timer) = limits.use_time_management() { + timer().init(limits.start, &uci_timer, board.turn(), board.moves_played()); } else { - TIMER.start_timer(limits.start); + timer().start_timer(limits.start); } let root_moves: MoveList = board.generate_moves(); From 1ac0a9f55e2332c953e5b9b71838023083d60e90 Mon Sep 17 00:00:00 2001 From: Stephen Fleischman Date: Mon, 26 Mar 2018 15:30:27 -0700 Subject: [PATCH 3/7] Added const indexing on the pawn & material tables Signed-off-by: stephenf --- pleco_engine/benches/eval_benches.rs | 10 ++--- pleco_engine/src/search/eval.rs | 4 +- pleco_engine/src/search/mod.rs | 4 +- pleco_engine/src/tables/material.rs | 15 ++++--- pleco_engine/src/tables/mod.rs | 60 +++++++++------------------ pleco_engine/src/tables/pawn_table.rs | 16 ++++--- 6 files changed, 48 insertions(+), 61 deletions(-) diff --git a/pleco_engine/benches/eval_benches.rs b/pleco_engine/benches/eval_benches.rs index 183c597..0056872 100644 --- a/pleco_engine/benches/eval_benches.rs +++ b/pleco_engine/benches/eval_benches.rs @@ -12,7 +12,7 @@ use pleco_engine::search::eval::Evaluation; fn bench_100_pawn_evals(b: &mut Bencher, boards: &Vec) { b.iter_with_setup(|| { - PawnTable::new(1 << 10) + PawnTable::new() }, |mut t| { #[allow(unused_variables)] let mut score: i64 = 0; @@ -27,7 +27,7 @@ fn bench_100_pawn_evals(b: &mut Bencher, boards: &Vec) { fn bench_100_pawn_king_evals(b: &mut Bencher, boards: &Vec) { b.iter_with_setup(|| { - PawnTable::new(1 << 10) + PawnTable::new() }, |mut t| { #[allow(unused_variables)] let mut score: i64 = 0; @@ -42,7 +42,7 @@ fn bench_100_pawn_king_evals(b: &mut Bencher, boards: &Vec) { fn bench_100_material_eval(b: &mut Bencher, boards: &Vec) { b.iter_with_setup(|| { - Material::new(1 << 11) + Material::new() }, |mut t| { #[allow(unused_variables)] let mut score: i64 = 0; @@ -56,8 +56,8 @@ fn bench_100_material_eval(b: &mut Bencher, boards: &Vec) { fn bench_100_eval(b: &mut Bencher, boards: &Vec) { b.iter_with_setup(|| { - let tp: PawnTable = black_box(PawnTable::new(1 << 10)); - let tm: Material = black_box(Material::new(1 << 11)); + let tp: PawnTable = black_box(PawnTable::new()); + let tm: Material = black_box(Material::new()); (tp, tm) }, |(mut tp, mut tm)| { #[allow(unused_variables)] diff --git a/pleco_engine/src/search/eval.rs b/pleco_engine/src/search/eval.rs index 7efe238..401d005 100644 --- a/pleco_engine/src/search/eval.rs +++ b/pleco_engine/src/search/eval.rs @@ -294,8 +294,8 @@ impl Evaluation { } pub fn trace(board: &Board) { - let mut pawn_table = PawnTable::new(1 << 4); - let mut material = Material::new(1 << 4); + let mut pawn_table = PawnTable::new(); + let mut material = Material::new(); let pawn_entry = { pawn_table.probe(&board) }; let material_entry = { material.probe(&board) }; let mut trace = Trace::new(); diff --git a/pleco_engine/src/search/mod.rs b/pleco_engine/src/search/mod.rs index a9bccf4..b7b68a9 100644 --- a/pleco_engine/src/search/mod.rs +++ b/pleco_engine/src/search/mod.rs @@ -188,8 +188,8 @@ impl Searcher { board: Board::start_pos(), time_man: timer(), tt: tt(), - pawns: PawnTable::new(16384), - material: Material::new(8192), + pawns: PawnTable::new(), + material: Material::new(), root_moves: UnsafeCell::new(RootMoveList::new()), selected_depth: 0, last_best_move: BitMove::null(), diff --git a/pleco_engine/src/tables/material.rs b/pleco_engine/src/tables/material.rs index 6cd54d3..7a82ec7 100644 --- a/pleco_engine/src/tables/material.rs +++ b/pleco_engine/src/tables/material.rs @@ -8,7 +8,7 @@ use pleco::core::score::*; use pleco::core::mono_traits::*; use pleco::tools::PreFetchable; -use super::TableBase; +use super::{TableBase,TableBaseConst}; pub const PHASE_END_GAME: u16 = 0; pub const PHASE_MID_GAME: u16 = 128; @@ -60,7 +60,13 @@ impl MaterialEntry { } } +// TODO: Use const-generics once it becomes available +impl TableBaseConst for MaterialEntry { + const ENTRY_COUNT: usize = 8192; +} +//pawns: PawnTable::new(16384), +//material: Material::new(8192), pub struct Material { table: TableBase, } @@ -85,15 +91,14 @@ impl Material { /// # Panics /// /// Panics if size is not a power of 2. - pub fn new(size: usize) -> Self { + pub fn new() -> Self { Material { - table: TableBase::new(size).unwrap() + table: TableBase::new().unwrap() } } pub fn clear(&mut self) { - let size = self.table.size(); - self.table.resize(size); + self.table.clear(); } pub fn probe(&mut self, board: &Board) -> &mut MaterialEntry { diff --git a/pleco_engine/src/tables/mod.rs b/pleco_engine/src/tables/mod.rs index d825857..0c8077a 100644 --- a/pleco_engine/src/tables/mod.rs +++ b/pleco_engine/src/tables/mod.rs @@ -81,26 +81,28 @@ pub trait NumStatCube: StatBoard { /// Objects placed inside must not implement `Drop`, or else undefined behavior follows. Indexing is done /// with `u64`s, and returns a value using a mask of the lower log2(table size) bits. Collisions /// are possible using this structure, although very rare. -pub struct TableBase { +pub struct TableBase { table: NonNull, - size: usize } -impl TableBase { +pub trait TableBaseConst { + const ENTRY_COUNT: usize; +} + +impl TableBase { /// Constructs a new `TableBase`. The size must be a power of 2, or else `None` is /// returned. /// /// # Safety /// /// Size must be a power of 2/ - pub fn new(size: usize) -> Option> { - if size.count_ones() != 1 { + pub fn new() -> Option> { + if T::ENTRY_COUNT.count_ones() != 1 { None } else { unsafe { let table = TableBase { - table: TableBase::alloc(size), - size: size + table: TableBase::alloc(), }; Some(table) } @@ -110,13 +112,13 @@ impl TableBase { /// Returns the size of the Table. #[inline] pub fn size(&self) -> usize { - self.size + T::ENTRY_COUNT } /// Gets a mutable reference to an entry with a certain key. #[inline] pub fn get_mut(&mut self, key: u64) -> &mut T { - let index: usize = (key & (self.size as u64 - 1)) as usize; + let index: usize = (key & (T::ENTRY_COUNT as u64 - 1)) as usize; unsafe { &mut *self.table.as_ptr().offset(index as isize) } @@ -130,28 +132,20 @@ impl TableBase { /// dropped prematurely. #[inline] pub unsafe fn get_ptr(&self, key: u64) -> *mut T { - let index: usize = (key & (self.size() as u64 - 1)) as usize; + let index: usize = (key & (T::ENTRY_COUNT as u64 - 1)) as usize; self.table.as_ptr().offset(index as isize) } - /// Re-sizes the table to a particular size, which must be a power of 2. Also clears all - /// the entries inside of the table. - /// - /// # Safety - /// - /// Panics if `size` is not a power of 2. - pub fn resize(&mut self, size: usize) { - assert_eq!(size.count_ones(), 1); + pub fn clear(&mut self) { unsafe { - self.de_alloc(); - self.table = TableBase::alloc(size); + let t_ptr = self.get_ptr(0); + ptr::write_bytes(t_ptr, 0, T::ENTRY_COUNT); } - self.size = size; } // allocates space. - unsafe fn alloc(size: usize) -> NonNull { - let ptr = Heap.alloc_zeroed(Layout::array::(size).unwrap()); + unsafe fn alloc() -> NonNull { + let ptr = Heap.alloc_zeroed(Layout::array::(T::ENTRY_COUNT).unwrap()); let new_ptr = match ptr { Ok(ptr) => ptr, Err(err) => Heap.oom(err), @@ -162,30 +156,14 @@ impl TableBase { /// de-allocates the current table. unsafe fn de_alloc(&mut self) { Heap.dealloc(self.table.as_ptr() as *mut _, - Layout::array::(self.size).unwrap()); + Layout::array::(T::ENTRY_COUNT).unwrap()); } } -impl Drop for TableBase { +impl Drop for TableBase { fn drop(&mut self) { unsafe { self.de_alloc(); } } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn table_base_allocs() { - for i in 0..14 { - let size: usize = 1 << i; - let mut t = TableBase::::new(size).unwrap(); - for x in 0..(3*size) { - *t.get_mut(x as u64) += 1 as u64; - } - } - } -} \ No newline at end of file diff --git a/pleco_engine/src/tables/pawn_table.rs b/pleco_engine/src/tables/pawn_table.rs index bcbdf7b..3b33c18 100644 --- a/pleco_engine/src/tables/pawn_table.rs +++ b/pleco_engine/src/tables/pawn_table.rs @@ -17,7 +17,7 @@ use pleco::core::CastleType; use pleco::helper::prelude::*; use pleco::tools::PreFetchable; -use super::TableBase; +use super::{TableBase,TableBaseConst}; @@ -133,9 +133,9 @@ impl PawnTable { /// # Panics /// /// Panics if size is not a power of 2. - pub fn new(size: usize) -> Self { + pub fn new() -> Self { PawnTable { - table: TableBase::new(size).unwrap() + table: TableBase::new().unwrap() } } @@ -148,8 +148,7 @@ impl PawnTable { /// Clears the table and resets to another size. pub fn clear(&mut self) { - let size = self.table.size(); - self.table.resize(size); + self.table.clear(); } /// Retrieves the entry of a specified key. If the `Entry` doesn't a matching key, @@ -220,6 +219,11 @@ pub struct PawnEntry { open_files: u8 } +impl TableBaseConst for PawnEntry { + const ENTRY_COUNT: usize = 16384; +} + + impl PawnEntry { /// Returns the current score of the pawn structure. @@ -494,7 +498,7 @@ mod tests { #[test] fn pawn_eval() { - let mut t: PawnTable = PawnTable::new(1 << 7); + let mut t: PawnTable = PawnTable::new(); let boards: Vec = Board::random().pseudo_random(2222212).many(9); let mut score: i64 = 0; boards.iter().for_each(|b| { From b7c567e170ff5a5cc76c20e691a1d98203e0d972 Mon Sep 17 00:00:00 2001 From: Stephen Fleischman Date: Mon, 26 Mar 2018 15:30:27 -0700 Subject: [PATCH 4/7] Benches go to bigger depth. Added value_{to/from}_tt, and mated_in / mate_in. Signed-off-by: stephenf --- README.md | 6 +-- pleco_engine/README.md | 17 ++++--- pleco_engine/benches/bench_engine_main.rs | 12 +++-- pleco_engine/benches/multimove_benches.rs | 6 +-- pleco_engine/benches/startpos_benches.rs | 4 +- pleco_engine/src/search/mod.rs | 54 ++++++++++++++++++----- pleco_engine/src/tables/material.rs | 1 + pleco_engine/src/tables/mod.rs | 13 ++---- pleco_engine/src/tables/pawn_table.rs | 1 - 9 files changed, 71 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index a0f4ca0..ce30345 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,14 @@ The Library aims to have the following features upon completion - [ ] PGN Parsing The AI Bot aims to have the following features: -- [x] Alpha-Beta pruning -- [x] Multi-threaded search with rayon.rs +- [x] Multi-threaded search using a shared hash-table - [x] Queiscience-search -- [x] MVV-LVA sorting - [x] Iterative Deepening - [x] Aspiration Windows - [x] Futility Pruning - [x] Transposition Tables -- [ ] Null Move Heuristic - [x] Killer Moves +- [ ] Null Move Heuristic Standalone Installation and Use ------- diff --git a/pleco_engine/README.md b/pleco_engine/README.md index fa0c3bb..3f524ff 100644 --- a/pleco_engine/README.md +++ b/pleco_engine/README.md @@ -22,23 +22,26 @@ Planned & Implemented features The AI aims to have the following features: -- [x] Alpha-Beta pruning -- [x] Multi-threaded search with rayon.rs -- [ ] Queiscience-search -- [x] MVV-LVA sorting +- [x] Multi-threaded search using a shared hash-table +- [x] Queiscience-search - [x] Iterative Deepening - [x] Aspiration Windows - [x] Futility Pruning - [x] Transposition Tables -- [ ] Null Move Heuristic - [x] Killer Moves +- [ ] Null Move Heuristic Standalone Installation and Use ------- -Currently, Pleco's use as a standalone program is limited in functionality. A UCI client is needed to properly interact with the program. As a recommendation, check out [Arena](http://www.playwitharena.com/). +Currently, Pleco's use as a standalone program is limited in functionality. A UCI client is needed to properly interact with the program. +As a recommendation, check out [Arena](http://www.playwitharena.com/). + +The easiest way to use the engine would be to check out the "releases" tab, +[here](https://github.com/sfleischman105/Pleco/releases). -Firstly, clone the repo and navigate into the created folder with the following commands: +If you would rather build it yourself (for a specific architecture, or otherwise), clone the repo +and navigate into the created folder with the following commands: ``` $ git clone https://github.com/sfleischman105/Pleco --branch master diff --git a/pleco_engine/benches/bench_engine_main.rs b/pleco_engine/benches/bench_engine_main.rs index 6d83b90..a22efc3 100644 --- a/pleco_engine/benches/bench_engine_main.rs +++ b/pleco_engine/benches/bench_engine_main.rs @@ -12,17 +12,21 @@ trait DepthLimit { fn depth() -> u16; } -struct Depth3 {} -struct Depth4 {} + + struct Depth5 {} struct Depth6 {} struct Depth7 {} +struct Depth8 {} +struct Depth9 {} + + -impl DepthLimit for Depth3 { fn depth() -> u16 {3} } -impl DepthLimit for Depth4 { fn depth() -> u16 {4} } impl DepthLimit for Depth5 { fn depth() -> u16 {5} } impl DepthLimit for Depth6 { fn depth() -> u16 {6} } impl DepthLimit for Depth7 { fn depth() -> u16 {7} } +impl DepthLimit for Depth8 { fn depth() -> u16 {8} } +impl DepthLimit for Depth9 { fn depth() -> u16 {9} } criterion_main!{ eval_benches::eval_benches, diff --git a/pleco_engine/benches/multimove_benches.rs b/pleco_engine/benches/multimove_benches.rs index d7b755e..b49acb9 100644 --- a/pleco_engine/benches/multimove_benches.rs +++ b/pleco_engine/benches/multimove_benches.rs @@ -49,19 +49,19 @@ fn search_startpos_3moves_engine(b: &mut Bencher) { } fn bench_engine_evaluations(c: &mut Criterion) { - c.bench_function("Search MuliMove Depth 4", search_startpos_3moves_engine::); c.bench_function("Search MuliMove Depth 5", search_startpos_3moves_engine::); c.bench_function("Search MuliMove Depth 6", search_startpos_3moves_engine::); c.bench_function("Search MuliMove Depth 7", search_startpos_3moves_engine::); - c.bench_function("Search KiwiPete MuliMove Depth 4", search_kiwipete_3moves_engine::); + c.bench_function("Search MuliMove Depth 8", search_startpos_3moves_engine::); c.bench_function("Search KiwiPete MuliMove Depth 5", search_kiwipete_3moves_engine::); c.bench_function("Search KiwiPete MuliMove Depth 6", search_kiwipete_3moves_engine::); c.bench_function("Search KiwiPete MuliMove Depth 7", search_kiwipete_3moves_engine::); + c.bench_function("Search KiwiPete MuliMove Depth 8", search_kiwipete_3moves_engine::); } criterion_group!(name = search_multimove; config = Criterion::default() - .sample_size(22) + .sample_size(26) .warm_up_time(Duration::from_millis(100)); targets = bench_engine_evaluations ); diff --git a/pleco_engine/benches/startpos_benches.rs b/pleco_engine/benches/startpos_benches.rs index 0e0d454..a4b265e 100644 --- a/pleco_engine/benches/startpos_benches.rs +++ b/pleco_engine/benches/startpos_benches.rs @@ -25,11 +25,11 @@ fn search_singular_engine(b: &mut Bencher) { } fn bench_engine_evaluations(c: &mut Criterion) { - c.bench_function("Search Singular Depth 3", search_singular_engine::); - c.bench_function("Search Singular Depth 4", search_singular_engine::); c.bench_function("Search Singular Depth 5", search_singular_engine::); c.bench_function("Search Singular Depth 6", search_singular_engine::); c.bench_function("Search Singular Depth 7", search_singular_engine::); + c.bench_function("Search Singular Depth 8", search_singular_engine::); + c.bench_function("Search Singular Depth 9", search_singular_engine::); } criterion_group!(name = search_singular; diff --git a/pleco_engine/src/search/mod.rs b/pleco_engine/src/search/mod.rs index b7b68a9..68dfa86 100644 --- a/pleco_engine/src/search/mod.rs +++ b/pleco_engine/src/search/mod.rs @@ -447,7 +447,7 @@ impl Searcher { if !self.stop() { let score_diff: i32 = best_value - self.previous_score; - let improving_factor: i64 = (246).max((832).min( + let improving_factor: i64 = (241).max((800).min( 306 + 119 * self.failed_low as i64 - 6 * score_diff as i64)); @@ -542,8 +542,8 @@ impl Searcher { // Mate distance pruning. This ensures that checkmates closer to the root // have a higher value than otherwise. - alpha = alpha.max(-MATE + ply as i32); - beta = beta.min(MATE - ply as i32); + alpha = alpha.max(mated_in(ply)); + beta = beta.min(mate_in(ply + 1)); if alpha >= beta { return alpha } @@ -573,7 +573,7 @@ impl Searcher { excluded_move = ss.excluded_move; zob = self.board.zobrist() ^ (excluded_move.get_raw() as u64).wrapping_shl(16); let (tt_hit, tt_entry): (bool, &mut Entry) = self.tt.probe(zob); - let tt_value: Value = if tt_hit {tt_entry.score as i32} else {NONE}; + let tt_value: Value = if tt_hit {value_from_tt(tt_entry.score, ss.ply)} else {NONE}; let tt_move: BitMove = if at_root {self.root_moves().first().bit_move} else if tt_hit {tt_entry.best_move} else {BitMove::null()}; @@ -895,7 +895,7 @@ impl Searcher { if moves_played == 1 || value > alpha { rm.depth_reached = depth; rm.score = value; - if moves_played > 1 && self.main_thread() { + if moves_played > 1 && self.main_thread() && depth > 5 { incr_bmc = true; } } else { @@ -943,7 +943,7 @@ impl Searcher { if excluded_move != BitMove::null() { return alpha; } else if in_check { - return -MATE as i32 + (ply as i32); + return mated_in(ss.ply); } else { return DRAW as i32; } @@ -984,7 +984,7 @@ impl Searcher { if excluded_move != BitMove::null() { - tt_entry.place(zob, best_move, best_value as i16, + tt_entry.place(zob, best_move, value_to_tt(best_value, ss.ply), ss.static_eval as i16, depth as i16, node_bound, self.tt.time_age()); } @@ -1008,7 +1008,7 @@ impl Searcher { let ply: u16 = ss.ply; let zob: u64 = self.board.zobrist(); let (tt_hit, tt_entry): (bool, &mut Entry) = self.tt.probe(zob); - let tt_value: Value = if tt_hit {tt_entry.score as i32} else {NONE}; + let tt_value: Value = if tt_hit {value_from_tt(tt_entry.score, ss.ply)} else {NONE}; let mut value: Value; let mut best_value: Value; @@ -1074,7 +1074,7 @@ impl Searcher { if best_value >= beta { if !tt_hit { - tt_entry.place(zob, BitMove::null(), best_value as i16, + tt_entry.place(zob, BitMove::null(), value_to_tt(best_value, ss.ply), pos_eval as i16, -6, NodeBound::LowerBound, self.tt.time_age()); } @@ -1162,7 +1162,7 @@ impl Searcher { best_move = mov; alpha = value; } else { - tt_entry.place(zob, mov, best_value as i16, + tt_entry.place(zob, mov, value_to_tt(best_value, ss.ply), ss.static_eval as i16, tt_depth as i16, NodeBound::LowerBound, self.tt.time_age()); return value; @@ -1173,14 +1173,14 @@ impl Searcher { // If in checkmate, return so if in_check && best_value == NEG_INFINITE { - return -MATE + ss.ply as i32; + return mated_in(ss.ply); } let node_bound = if is_pv && best_value > old_alpha {NodeBound::Exact} else {NodeBound::UpperBound}; - tt_entry.place(zob, best_move, best_value as i16, + tt_entry.place(zob, best_move, value_to_tt(best_value, ss.ply), ss.static_eval as i16, tt_depth, node_bound, self.tt.time_age()); @@ -1358,6 +1358,36 @@ fn correct_bound(tt_value: i32, val: i32, bound: NodeBound) -> bool { } } +fn mate_in(ply: u16) -> i32 { + MATE - ply as i32 +} + +fn mated_in(ply: u16) -> i32 { + -MATE + ply as i32 +} + +fn value_to_tt(value: i32, ply: u16) -> i16 { + if value >= MATE_IN_MAX_PLY { + (value + ply as i32) as i16 + } else if value <= MATED_IN_MAX_PLY { + (value - ply as i32) as i16 + } else { + value as i16 + } +} + +fn value_from_tt(value: i16, ply: u16) -> i32 { + if value as i32 == NONE { + NONE + } else if value as i32 >= MATE_IN_MAX_PLY { + value as i32 - ply as i32 + } else if value as i32 <= MATED_IN_MAX_PLY { + value as i32 + ply as i32 + } else { + value as i32 + } +} + #[inline] fn futility_margin(depth: i16, improving: bool) -> i32 { depth as i32 * (175 - 50 * improving as i32) diff --git a/pleco_engine/src/tables/material.rs b/pleco_engine/src/tables/material.rs index 7a82ec7..a75bd5b 100644 --- a/pleco_engine/src/tables/material.rs +++ b/pleco_engine/src/tables/material.rs @@ -55,6 +55,7 @@ impl MaterialEntry { Score(self.value, self.value) } + #[inline(always)] pub fn scale_factor(&self, player: Player) -> u8 { self.factor[player as usize] } diff --git a/pleco_engine/src/tables/mod.rs b/pleco_engine/src/tables/mod.rs index 0c8077a..4f1b0b3 100644 --- a/pleco_engine/src/tables/mod.rs +++ b/pleco_engine/src/tables/mod.rs @@ -109,18 +109,11 @@ impl TableBase { } } - /// Returns the size of the Table. - #[inline] - pub fn size(&self) -> usize { - T::ENTRY_COUNT - } - /// Gets a mutable reference to an entry with a certain key. - #[inline] + #[inline(always)] pub fn get_mut(&mut self, key: u64) -> &mut T { - let index: usize = (key & (T::ENTRY_COUNT as u64 - 1)) as usize; unsafe { - &mut *self.table.as_ptr().offset(index as isize) + &mut *self.get_ptr(key) } } @@ -130,7 +123,7 @@ impl TableBase { /// /// Unsafe due to returning a raw pointer that may dangle if the `TableBase` is /// dropped prematurely. - #[inline] + #[inline(always)] pub unsafe fn get_ptr(&self, key: u64) -> *mut T { let index: usize = (key & (T::ENTRY_COUNT as u64 - 1)) as usize; self.table.as_ptr().offset(index as isize) diff --git a/pleco_engine/src/tables/pawn_table.rs b/pleco_engine/src/tables/pawn_table.rs index 3b33c18..8ae79b7 100644 --- a/pleco_engine/src/tables/pawn_table.rs +++ b/pleco_engine/src/tables/pawn_table.rs @@ -294,7 +294,6 @@ impl PawnEntry { self.pawns_on_squares[player as usize][sq.square_color_index()] } - /// Returns the current king safety `Score` for a given player and king square. pub fn king_safety(&mut self, board: &Board, ksq: SQ) -> Score { if self.king_squares[P::player_idx()] == ksq From 9ce7cf0aa44e411a3cdf335fb859d29721336951 Mon Sep 17 00:00:00 2001 From: Stephen Fleischman Date: Mon, 26 Mar 2018 15:30:27 -0700 Subject: [PATCH 5/7] Better Razoring Signed-off-by: stephenf --- pleco_engine/src/search/mod.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/pleco_engine/src/search/mod.rs b/pleco_engine/src/search/mod.rs index 68dfa86..1fc5372 100644 --- a/pleco_engine/src/search/mod.rs +++ b/pleco_engine/src/search/mod.rs @@ -45,6 +45,7 @@ static START_PLY: [i16; THREAD_DIST] = [0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1 static mut REDUCTIONS: [[[[i16; 64]; 64]; 2]; 2] = [[[[0; 64]; 64]; 2]; 2]; // [pv][improving][depth][moveNumber] static mut FUTILITY_MOVE_COUNTS: [[i32; 16]; 2] = [[0; 16]; 2]; // [improving][depth] +static RAZOR_MARGIN: [i32; 3] = [0, 590, 604]; static CAPTURE_PRUNE_MARGIN: [i32; 7] = [ 0, @@ -656,9 +657,13 @@ impl Searcher { // Razoring. At the lowest depth before qsearch, If the evaluation + a margin still // isn't better than alpha, go straight to qsearch. if !is_pv - && depth <= 1 - && pos_eval + RAZORING_MARGIN <= alpha { - return self.qsearch::(alpha, alpha+1, ss, 0); + && depth < 3 + && pos_eval <= alpha - RAZOR_MARGIN[depth as usize] { + let r_alpha = alpha - (depth >= 2) as i32 * RAZOR_MARGIN[depth as usize]; + let v = self.qsearch::(r_alpha, r_alpha+1, ss, 0); + if depth < 2 || v <= r_alpha { + return v; + } } // Futility Pruning. Disregard moves that have little chance of raising the callee's @@ -809,7 +814,7 @@ impl Searcher { // At higher depths, do a search of a lower ply to see if this move is // worth searching. We don't do this for capturing or promotion moves. let do_full_depth: bool = if moves_played > 1 - && depth >= 4 + && depth >= 3 && (!capture_or_promotion || move_count_pruning) { let mut r: i16 = reduction::(improving, depth, moves_played); @@ -1007,8 +1012,6 @@ impl Searcher { let ply: u16 = ss.ply; let zob: u64 = self.board.zobrist(); - let (tt_hit, tt_entry): (bool, &mut Entry) = self.tt.probe(zob); - let tt_value: Value = if tt_hit {value_from_tt(tt_entry.score, ss.ply)} else {NONE}; let mut value: Value; let mut best_value: Value; @@ -1021,9 +1024,6 @@ impl Searcher { let in_check: bool = self.board.in_check(); - // Determine whether or not to include checking moves. - let tt_depth: i16 = if in_check || rev_depth >= 0 {0} else {-1}; - if ply >= MAX_PLY { if !in_check { return self.eval(); @@ -1032,6 +1032,12 @@ impl Searcher { } } + let (tt_hit, tt_entry): (bool, &mut Entry) = self.tt.probe(zob); + let tt_value: Value = if tt_hit {value_from_tt(tt_entry.score, ss.ply)} else {NONE}; + + // Determine whether or not to include checking moves. + let tt_depth: i16 = if in_check || rev_depth >= 0 {0} else {-1}; + // increment the next ply ss.incr().ply = ply + 1; ss.current_move = BitMove::null(); From 534a45a3f6cb1ae00ac3b695c2d8efb466eb6493 Mon Sep 17 00:00:00 2001 From: Stephen Fleischman Date: Mon, 26 Mar 2018 15:30:27 -0700 Subject: [PATCH 6/7] time Management changes Signed-off-by: stephenf --- pleco_engine/src/search/mod.rs | 10 +++++----- pleco_engine/src/time/time_management.rs | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pleco_engine/src/search/mod.rs b/pleco_engine/src/search/mod.rs index 1fc5372..d648698 100644 --- a/pleco_engine/src/search/mod.rs +++ b/pleco_engine/src/search/mod.rs @@ -355,7 +355,7 @@ impl Searcher { 'iterative_deepening: while !self.stop() && depth < max_depth { if self.main_thread() { - self.best_move_changes *= 0.500; + self.best_move_changes *= 0.440; self.failed_low = false; } @@ -448,7 +448,7 @@ impl Searcher { if !self.stop() { let score_diff: i32 = best_value - self.previous_score; - let improving_factor: i64 = (241).max((800).min( + let improving_factor: i64 = (240).max((787).min( 306 + 119 * self.failed_low as i64 - 6 * score_diff as i64)); @@ -458,20 +458,20 @@ impl Searcher { // If the bestMove is stable over several iterations, reduce time accordingly for i in 3..6 { if self.last_best_move_depth * i < self.depth_completed { - time_reduction *= 1.40; + time_reduction *= 1.41; } } // Use part of the gained time from a previous stable move for the current move let mut unstable_factor: f64 = 1.0 + self.best_move_changes; - unstable_factor *= self.previous_time_reduction.powf(0.46) / time_reduction; + unstable_factor *= self.previous_time_reduction.powf(0.40) / time_reduction; // Stop the search if we have only one legal move, or if available time elapsed if self.root_moves().len() == 1 || self.time_man.elapsed() >= (self.time_man.ideal_time() as f64 * unstable_factor as f64 - * improving_factor as f64 / 600.0) as i64 { + * improving_factor as f64 / 580.0) as i64 { threadpool().set_stop(true); break 'iterative_deepening; } diff --git a/pleco_engine/src/time/time_management.rs b/pleco_engine/src/time/time_management.rs index 1da0154..ca483d0 100644 --- a/pleco_engine/src/time/time_management.rs +++ b/pleco_engine/src/time/time_management.rs @@ -11,13 +11,15 @@ use std::f64; const MOVE_HORIZON: i64 = 50; -const MAX_RATIO: f64 = 6.49; +const MAX_RATIO: f64 = 6.32; const STEAL_RATIO: f64 = 0.34; // TODO: These should be made into UCIOptions const MIN_THINKING_TIME: i64 = 20; const MOVE_OVERHEAD: i64 = 100; -const SLOW_MOVER: i64 = 45; + +// Lower values means places less importance on the current move +const SLOW_MOVER: i64 = 30; #[derive(PartialEq)] enum TimeCalc { From 82f041caf04274a7003e113c79148e0ada44334d Mon Sep 17 00:00:00 2001 From: Stephen Fleischman Date: Mon, 26 Mar 2018 15:30:27 -0700 Subject: [PATCH 7/7] PV display changes, more time management changes. Signed-off-by: stephenf --- pleco_engine/src/search/mod.rs | 49 +++++++++++++++--------- pleco_engine/src/time/time_management.rs | 2 +- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/pleco_engine/src/search/mod.rs b/pleco_engine/src/search/mod.rs index d648698..c05c3ef 100644 --- a/pleco_engine/src/search/mod.rs +++ b/pleco_engine/src/search/mod.rs @@ -267,7 +267,6 @@ impl Searcher { // iterate through each thread, and find the best move available (based on score) let mut best_move = self.root_moves().first().bit_move; let mut best_score = self.root_moves().first().score; - let mut best_depth = self.depth_completed; if !self.limit.limits_type.is_depth() { let mut best_thread: &Searcher = &self; threadpool().threads.iter() @@ -281,17 +280,16 @@ impl Searcher { }); best_move = best_thread.root_moves().first().bit_move; best_score = best_thread.root_moves().first().score; - best_depth = best_thread.depth_completed; + + // Cases where the MainTHread did not have the correct best move, display it. + if self.use_stdout() && best_thread.id != self.id { + best_thread.pv(best_thread.depth_completed, NEG_INFINITE, INFINITE); + } } self.previous_score = best_score; self.best_move = best_move; - // Cases where the MainTHread did not have the correct best move, display it. - if self.use_stdout() && best_move != self.root_moves().first().bit_move { - println!("{}",self.pv(best_depth, NEG_INFINITE, INFINITE)); - } - if self.use_stdout() { println!("bestmove {}", best_move.to_string()); @@ -390,7 +388,7 @@ impl Searcher { if self.use_stdout() && self.main_thread() && (best_value <= alpha || best_value >= beta) && self.time_man.elapsed() > 3000 { - println!("{}",self.pv(depth, alpha, beta)); + self.pv(depth, alpha, beta); } // Check for incorrect search window. If the value if less than alpha @@ -416,9 +414,9 @@ impl Searcher { // Main Thread provides an update to the GUI if self.use_stdout() && self.main_thread() && self.time_man.elapsed() > 6 { if self.stop() { - println!("{}",self.pv(depth, NEG_INFINITE, INFINITE)); + self.pv(depth, NEG_INFINITE, INFINITE); } else { - println!("{}",self.pv(depth, alpha, beta)); + self.pv(depth, alpha, beta); } } @@ -448,7 +446,7 @@ impl Searcher { if !self.stop() { let score_diff: i32 = best_value - self.previous_score; - let improving_factor: i64 = (240).max((787).min( + let improving_factor: i64 = (232).max((787).min( 306 + 119 * self.failed_low as i64 - 6 * score_diff as i64)); @@ -458,7 +456,7 @@ impl Searcher { // If the bestMove is stable over several iterations, reduce time accordingly for i in 3..6 { if self.last_best_move_depth * i < self.depth_completed { - time_reduction *= 1.41; + time_reduction *= 1.42; } } @@ -471,7 +469,7 @@ impl Searcher { || self.time_man.elapsed() >= (self.time_man.ideal_time() as f64 * unstable_factor as f64 - * improving_factor as f64 / 580.0) as i64 { + * improving_factor as f64 / 600.0) as i64 { threadpool().set_stop(true); break 'iterative_deepening; } @@ -1309,22 +1307,35 @@ impl Searcher { } /// Useful information to tell to the GUI - fn pv(&self, depth: i16, alpha: i32, beta: i32) -> String { + fn pv(&self, depth: i16, alpha: i32, beta: i32) { let root_move: &RootMove= self.root_moves().first(); let elapsed = self.time_man.elapsed() as u64; let nodes = threadpool().nodes(); - let mut s = String::from("info"); let mut score = if root_move.score == NEG_INFINITE { root_move.prev_score } else { root_move.score }; - score *= 100; - score /= PAWN_EG; + if score == NEG_INFINITE { + return; + } + let mut s = String::from("info"); s.push_str(&format!(" depth {}", depth)); - s.push_str(&format!(" score cp {}", score)); + if score.abs() < MATE - MAX_PLY as i32 { + score *= 100; + score /= PAWN_EG; + s.push_str(&format!(" score cp {}", score)); + } else { + let mut mate_in = if score > 0 { + MATE - score + 1 + } else { + -MATE - score + }; + mate_in /= 2; + s.push_str(&format!(" score mate {}", mate_in)); + } if root_move.score >= beta { s.push_str(" lowerbound"); } else if root_move.score <= alpha { @@ -1337,7 +1348,7 @@ impl Searcher { } s.push_str(&format!(" time {}", elapsed)); s.push_str(&format!(" pv {}", root_move.bit_move.to_string())); - s + println!("{}",s); } } diff --git a/pleco_engine/src/time/time_management.rs b/pleco_engine/src/time/time_management.rs index ca483d0..0f983e4 100644 --- a/pleco_engine/src/time/time_management.rs +++ b/pleco_engine/src/time/time_management.rs @@ -19,7 +19,7 @@ const MIN_THINKING_TIME: i64 = 20; const MOVE_OVERHEAD: i64 = 100; // Lower values means places less importance on the current move -const SLOW_MOVER: i64 = 30; +const SLOW_MOVER: i64 = 22; #[derive(PartialEq)] enum TimeCalc {