diff --git a/src/book.rs b/src/book.rs index b626381..3df4640 100644 --- a/src/book.rs +++ b/src/book.rs @@ -2,6 +2,7 @@ use crate::engine::board::*; use crate::engine::eval::*; use crate::engine::hand::*; use crate::engine::search::*; +use crate::engine::table::*; use crate::engine::think::*; use crate::record::*; use crate::setup::*; @@ -169,14 +170,17 @@ impl Book { } } -fn search(board: Board, think_time_limit: u128, solve_obj: &SolveObj, rt: &Runtime, sub_solver: &Arc) -> Hand { - solve_obj.eval_cache.inc_gen(); +fn search( + board: Board, + think_time_limit: u128, + solve_obj: &mut SolveObj, + rt: &Runtime, + sub_solver: &Arc, +) -> Hand { + solve_obj.cache_gen += 1; if board.empty().count_ones() <= 18 { - solve_obj.res_cache.inc_gen(); let mut solve_obj = solve_obj.clone(); - rt.block_on(async move { - solve_with_move(board, &mut solve_obj, &sub_solver.clone()).await - }) + rt.block_on(async move { solve_with_move(board, &mut solve_obj, &sub_solver.clone()).await }) } else { let start = Instant::now(); let timer = Timer { @@ -188,6 +192,7 @@ fn search(board: Board, think_time_limit: u128, solve_obj: &SolveObj, rt: &Runti cache: solve_obj.eval_cache.clone(), timer: Some(timer), node_count: 0, + cache_gen: solve_obj.cache_gen, }; let (_score, hand, _depth) = searcher.iterative_think(board, EVAL_SCORE_MIN, EVAL_SCORE_MAX, false); hand @@ -210,7 +215,7 @@ fn gen_opening(rng: &mut SmallRng) -> (Board, Vec) { fn play_with_book( book: Arc>, think_time_limit: u128, - solve_obj: &SolveObj, + solve_obj: &mut SolveObj, rt: &Runtime, rng: &mut SmallRng, sub_solver: &Arc, @@ -249,7 +254,21 @@ fn grow_book(in_book_path: &Path, out_book_path: &Path, repeat: usize) -> Result let mut rng = SmallRng::seed_from_u64(0xbeefbeef + i as u64); let think_time_limit = 1 << rng.gen_range(7..=9); eprintln!("i={}, tl={}", i, think_time_limit); - play_with_book(book.clone(), think_time_limit, &solve_obj, &rt, &mut rng, &sub_solver.clone()); + let mut solve_obj = SolveObj::new( + Arc::new(ResCacheTable::new(256, 4096)), + Arc::new(EvalCacheTable::new(256, 4096)), + solve_obj.evaluator.clone(), + solve_obj.params.clone(), + 0, + ); + play_with_book( + book.clone(), + think_time_limit, + &mut solve_obj, + &rt, + &mut rng, + &sub_solver.clone(), + ); }); book.lock().unwrap().export(out_book_path)?; Ok(()) diff --git a/src/engine/endgame.rs b/src/engine/endgame.rs index 4b11dff..4009d8d 100644 --- a/src/engine/endgame.rs +++ b/src/engine/endgame.rs @@ -201,6 +201,7 @@ pub fn solve_inner( let (res, stat) = fastest_first(solve_obj, board, (alpha, beta), passed); update_table( solve_obj.res_cache.clone(), + solve_obj.cache_gen, board, res, None, @@ -217,6 +218,7 @@ pub fn solve_inner( if rem >= solve_obj.params.res_cache_limit { update_table( solve_obj.res_cache.clone(), + solve_obj.cache_gen, board, res, best, diff --git a/src/engine/midgame.rs b/src/engine/midgame.rs index 57868f3..4f74a0f 100644 --- a/src/engine/midgame.rs +++ b/src/engine/midgame.rs @@ -213,6 +213,7 @@ where if rem >= solve_obj.params.res_cache_limit { update_table( solve_obj.res_cache.clone(), + solve_obj.cache_gen, board, res, best, diff --git a/src/engine/search.rs b/src/engine/search.rs index a25fcfe..3e28c40 100644 --- a/src/engine/search.rs +++ b/src/engine/search.rs @@ -55,6 +55,7 @@ pub struct SolveObj { pub evaluator: Arc, pub last_cache: Arc, pub params: SearchParams, + pub cache_gen: u8, } impl SolveObj { @@ -63,6 +64,7 @@ impl SolveObj { eval_cache: Arc, evaluator: Arc, params: SearchParams, + cache_gen: u8, ) -> SolveObj { SolveObj { res_cache, @@ -70,6 +72,7 @@ impl SolveObj { evaluator, last_cache: Arc::new(LastCache::new()), params, + cache_gen, } } } @@ -237,6 +240,7 @@ pub fn move_ordering_impl(solve_obj: &mut SolveObj, board: Board, _old_best: Opt cache: solve_obj.eval_cache.clone(), timer: None, node_count: 0, + cache_gen: solve_obj.cache_gen, }; let score = searcher .think( diff --git a/src/engine/table.rs b/src/engine/table.rs index 1374859..0b2f3cc 100644 --- a/src/engine/table.rs +++ b/src/engine/table.rs @@ -4,13 +4,12 @@ use crate::engine::hand::*; use crc64::Crc64; use spin::Mutex; use std::io::Write; -use std::sync::atomic::{AtomicU16, Ordering}; use std::sync::Arc; pub trait CacheElement: Clone + Default { fn has_key(&self, board: Board) -> bool; fn get_key(&self) -> Board; - fn update(&mut self, that: &Self, gen: u16); + fn update(&mut self, that: &Self); } #[derive(Clone)] @@ -18,7 +17,7 @@ pub struct EvalCache { pub board: Board, pub lower: i16, pub upper: i16, - pub gen: u16, + pub gen: u8, pub best: Option, pub depth: i32, } @@ -48,7 +47,7 @@ impl CacheElement for EvalCache { self.board } - fn update(&mut self, that: &Self, gen: u16) { + fn update(&mut self, that: &Self) { if that.board == self.board { if that.depth >= self.depth { if that.depth == self.depth { @@ -60,14 +59,14 @@ impl CacheElement for EvalCache { } else { *self = that.clone(); } - self.gen = gen; + self.gen = that.gen; } } else { let empty_self = popcnt(self.board.empty()); let empty_that = popcnt(that.board.empty()); - if empty_that >= empty_self || gen > self.gen { + if empty_that >= empty_self || that.gen > self.gen { *self = that.clone(); - self.gen = gen; + self.gen = that.gen; } } } @@ -78,7 +77,7 @@ pub struct ResCache { pub board: Board, pub lower: i8, pub upper: i8, - pub gen: u16, + pub gen: u8, pub best: Option, } @@ -106,20 +105,20 @@ impl CacheElement for ResCache { self.board } - fn update(&mut self, that: &Self, gen: u16) { + fn update(&mut self, that: &Self) { if that.board == self.board { let lower = self.lower.max(that.lower); let upper = self.upper.min(that.upper); *self = that.clone(); self.lower = lower; self.upper = upper; - self.gen = gen; + self.gen = that.gen; } else { let empty_self = popcnt(self.board.empty()); let empty_that = popcnt(that.board.empty()); - if empty_that >= empty_self || gen > self.gen { + if empty_that >= empty_self || that.gen > self.gen { *self = that.clone(); - self.gen = gen; + self.gen = that.gen; } } } @@ -149,17 +148,16 @@ impl CacheArray { } } - fn update(&mut self, new_elem: &T, hash: u64, gen: u16) { + fn update(&mut self, new_elem: &T, hash: u64) { let index = (hash % self.cycle) as usize; let elem = &mut self.ary[index]; - elem.update(new_elem, gen); + elem.update(new_elem); } } pub struct CacheTable { arrays: Vec>>, buckets: u64, - pub gen: AtomicU16, } impl CacheTable { @@ -171,7 +169,6 @@ impl CacheTable { CacheTable:: { arrays: vec, buckets: buckets as u64, - gen: 1.into(), } } @@ -193,13 +190,7 @@ impl CacheTable { let hash = crc64.get(); let bucket_id = (hash % self.buckets) as usize; let bucket_hash = hash / self.buckets; - self.arrays[bucket_id] - .lock() - .update(&cache, bucket_hash, self.gen.load(Ordering::SeqCst)); - } - - pub fn inc_gen(&self) { - self.gen.fetch_add(1, Ordering::SeqCst); + self.arrays[bucket_id].lock().update(&cache, bucket_hash); } } @@ -207,7 +198,7 @@ pub type EvalCacheTable = CacheTable; pub type ResCacheTable = CacheTable; fn make_record( - gen: u16, + gen: u8, board: Board, mut res: i8, best: Option, @@ -233,19 +224,13 @@ fn make_record( pub fn update_table( res_cache: Arc, + cache_gen: u8, board: Board, res: i8, best: Option, (alpha, beta): (i8, i8), range: (i8, i8), ) { - let record = make_record( - res_cache.gen.load(Ordering::SeqCst), - board, - res, - best, - (alpha, beta), - range, - ); + let record = make_record(cache_gen, board, res, best, (alpha, beta), range); res_cache.update(record); } diff --git a/src/engine/think.rs b/src/engine/think.rs index f44eac3..9832e64 100644 --- a/src/engine/think.rs +++ b/src/engine/think.rs @@ -23,6 +23,7 @@ pub struct Searcher { pub cache: Arc, pub timer: Option, pub node_count: usize, + pub cache_gen: u8, } pub const DEPTH_SCALE: i32 = 256; @@ -233,7 +234,7 @@ impl Searcher { board, lower: range.0, upper: range.1, - gen: self.cache.gen.load(std::sync::atomic::Ordering::SeqCst), + gen: self.cache_gen, best: Some(best), depth, }; @@ -280,6 +281,7 @@ impl Searcher { pub fn iterative_think(&mut self, board: Board, alpha: i16, beta: i16, passed: bool) -> (i16, Hand, i8) { let min_depth = 3; + let max_depth = 60; let mut current_depth = min_depth; let (mut score, mut hand) = self @@ -290,8 +292,7 @@ impl Searcher { return (score, hand, current_depth as i8); } - for depth in (min_depth + 1).. { - self.cache.inc_gen(); + for depth in (min_depth + 1)..=max_depth { let t = match self.think_with_move(board, alpha, beta, passed, depth * DEPTH_SCALE) { Some(t) => t, _ => return (score, hand, current_depth as i8), diff --git a/src/main.rs b/src/main.rs index c40f3bd..25746ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,8 +100,7 @@ fn solve_ffo(name: &str, index: &mut usize, solve_obj: &mut SolveObj, workers: & end.subsec_millis(), nodes_per_sec / 1_000_000 ); - solve_obj.eval_cache.inc_gen(); - solve_obj.res_cache.inc_gen(); + solve_obj.cache_gen += 1; stats.push(Stat { nodes: stat.node_count, elapsed: end.as_secs_f64(), diff --git a/src/play.rs b/src/play.rs index 634c9e4..cac5a0c 100644 --- a/src/play.rs +++ b/src/play.rs @@ -24,7 +24,7 @@ use tokio::runtime::Runtime; pub fn play(matches: &ArgMatches) -> Board { let player_turn = matches.get_one::("player").unwrap() == "B"; - let solve_obj = setup_default(); + let mut solve_obj = setup_default(); let sub_solver = Arc::new(SubSolver::new(&[])); let mut board = BoardWithColor::initial_state(); @@ -57,6 +57,7 @@ pub fn play(matches: &ArgMatches) -> Board { cache: solve_obj.eval_cache.clone(), timer: Some(timer), node_count: 0, + cache_gen: solve_obj.cache_gen, }; let (score, best, depth) = searcher.iterative_think(board.board, EVAL_SCORE_MIN, EVAL_SCORE_MAX, false); let scaled_score = score as f64 / SCALE as f64; @@ -68,8 +69,7 @@ pub fn play(matches: &ArgMatches) -> Board { .unwrap() .block_on(solve_with_move(board.board, &mut solve_obj, &sub_solver)) }; - solve_obj.eval_cache.inc_gen(); - solve_obj.res_cache.inc_gen(); + solve_obj.cache_gen += 1; best }; match hand { @@ -86,7 +86,7 @@ pub fn play(matches: &ArgMatches) -> Board { } pub fn self_play(matches: &ArgMatches) -> Board { - let solve_obj = setup_default(); + let mut solve_obj = setup_default(); let worker_urls = matches .get_many("workers") .unwrap() @@ -110,6 +110,7 @@ pub fn self_play(matches: &ArgMatches) -> Board { cache: solve_obj.eval_cache.clone(), timer: Some(timer), node_count: 0, + cache_gen: solve_obj.cache_gen, }; let (score, best, depth) = searcher.iterative_think(board.board, EVAL_SCORE_MIN, EVAL_SCORE_MAX, false); let secs = start.elapsed().as_secs_f64(); @@ -125,8 +126,7 @@ pub fn self_play(matches: &ArgMatches) -> Board { .unwrap() .block_on(solve_with_move(board.board, &mut solve_obj, &sub_solver)) }; - solve_obj.eval_cache.inc_gen(); - solve_obj.res_cache.inc_gen(); + solve_obj.cache_gen += 1; let hand = best; match hand { Hand::Pass => board = board.pass_unchecked(), @@ -141,7 +141,7 @@ pub fn self_play(matches: &ArgMatches) -> Board { board.board } -fn self_play_worker(solve_obj: SolveObj, sub_solver: Arc, initial_record: &[Hand]) -> (String, i8) { +fn self_play_worker(mut solve_obj: SolveObj, sub_solver: Arc, initial_record: &[Hand]) -> (String, i8) { use std::fmt::Write; let mut board = BoardWithColor::initial_state(); let mut record_str = String::new(); @@ -170,6 +170,7 @@ fn self_play_worker(solve_obj: SolveObj, sub_solver: Arc, initial_rec cache: solve_obj.eval_cache.clone(), timer: Some(timer), node_count: 0, + cache_gen: solve_obj.cache_gen, }; let (_score, best, _depth) = searcher.iterative_think(board.board, EVAL_SCORE_MIN, EVAL_SCORE_MAX, false); best @@ -179,8 +180,7 @@ fn self_play_worker(solve_obj: SolveObj, sub_solver: Arc, initial_rec .unwrap() .block_on(solve_with_move(board.board, &mut obj, &sub_solver)) }; - solve_obj.eval_cache.inc_gen(); - solve_obj.res_cache.inc_gen(); + solve_obj.cache_gen += 1; let hand = best; match hand { Hand::Pass => board = board.pass_unchecked(), @@ -259,7 +259,7 @@ macro_rules! parse_input { } pub fn codingame(_matches: &ArgMatches) -> Result<(), Box> { - let solve_obj = setup_default(); + let mut solve_obj = setup_default(); let sub_solver = Arc::new(SubSolver::new(&[])); let mut reader = BufReader::new(std::io::stdin()); let book = Book::import(Path::new("medium2.book"))?; @@ -324,6 +324,7 @@ pub fn codingame(_matches: &ArgMatches) -> Result<(), Box cache: solve_obj.eval_cache.clone(), timer: Some(timer), node_count: 0, + cache_gen: solve_obj.cache_gen, }; let (score, best, depth) = searcher.iterative_think(board.board, EVAL_SCORE_MIN, EVAL_SCORE_MAX, false); eprintln!( @@ -337,8 +338,7 @@ pub fn codingame(_matches: &ArgMatches) -> Result<(), Box .unwrap() .block_on(solve_with_move(board.board, &mut solve_obj, &sub_solver)) }; - solve_obj.eval_cache.inc_gen(); - solve_obj.res_cache.inc_gen(); + solve_obj.cache_gen += 1; match best { Hand::Play(pos) => { println!("{}", pos_to_str(pos).to_ascii_lowercase()); diff --git a/src/remote.rs b/src/remote.rs index 62f2546..224353f 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -51,7 +51,7 @@ async fn worker_body() { let evaluator = Arc::new(Evaluator::new("table-220710")); let res_cache = Arc::new(ResCacheTable::new(256, 65536)); let eval_cache = Arc::new(EvalCacheTable::new(256, 65536)); - let solve_obj = SolveObj::new(res_cache, eval_cache, evaluator, search_params); + let solve_obj = SolveObj::new(res_cache, eval_cache, evaluator, search_params, 0); let addr = SocketAddr::from(([0, 0, 0, 0], 7733)); diff --git a/src/setup.rs b/src/setup.rs index eff24f5..086db50 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -19,5 +19,5 @@ pub fn setup_default() -> SolveObj { ffs_ordering_limit: 6, static_ordering_limit: 5, }; - SolveObj::new(res_cache, eval_cache, evaluator, search_params) + SolveObj::new(res_cache, eval_cache, evaluator, search_params, 0) } diff --git a/src/train.rs b/src/train.rs index 900636e..dc1c6da 100644 --- a/src/train.rs +++ b/src/train.rs @@ -493,22 +493,21 @@ pub fn eval_stats(matches: &ArgMatches) -> Option<()> { let dataset: Vec<_> = dataset.into_iter().take(8192).collect(); eprintln!("Computing..."); - let eval_cache = Arc::new(EvalCacheTable::new(256, 65536)); let evaluator = Arc::new(Evaluator::new("table-single")); let depth_max = 8; let scores: Vec<_> = dataset .par_iter() .map(|&(board, _)| { let mut scores = Vec::new(); - let eval_cache = eval_cache.clone(); + let eval_cache = Arc::new(EvalCacheTable::new(256, 4096)); let mut searcher = Searcher { evaluator: evaluator.clone(), cache: eval_cache.clone(), timer: None, node_count: 0, + cache_gen: 0, }; for depth in 1..=depth_max { - eval_cache.inc_gen(); if let Some((evaluated, _)) = searcher.think( board, EVAL_SCORE_MIN,