use std::prelude::v1::*;
use std::mem;
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use branches::prefetch_read_data;
use crate::common::*;
use crate::piece_move::PieceMove;
use crate::transposition::{Transposition, Bound};
#[derive(Clone)]
pub struct TranspositionTable {
entries: Arc<SharedTable>,
age: Arc<AtomicU8>,
stats_lookups: u64,
stats_inserts: u64,
stats_hits : u64,
stats_collisions: u64
}
impl TranspositionTable {
pub fn with_capacity(capacity: usize) -> TranspositionTable {
let n = if capacity.is_power_of_two() {
capacity
} else {
capacity.next_power_of_two()
};
TranspositionTable {
entries: Arc::new(SharedTable::with_capacity(n)),
age: Arc::new(AtomicU8::new(0)),
stats_lookups: 0,
stats_inserts: 0,
stats_hits: 0,
stats_collisions: 0
}
}
pub fn with_memory(memory: usize) -> TranspositionTable {
let capacity = memory / mem::size_of::<Transposition>();
TranspositionTable::with_capacity(capacity)
}
pub fn prefetch(&self, hash: u64) {
let h = self.entries.get();
let n = self.len() as u64;
let k = (hash % n) as usize;
prefetch_read_data::<_, 0>(&h[k]);
}
pub fn get(&mut self, hash: u64) -> Option<Transposition> {
self.stats_lookups += 1;
let h = self.entries.get();
let n = self.len() as u64;
let k = (hash % n) as usize;
let t = h[k].clone();
if t.best_move().is_null() {
None
} else {
let (hash_word, data_word) = t.hash_and_data();
if hash_word ^ data_word != hash {
self.stats_collisions += 1;
None
} else {
self.stats_hits += 1;
Some(t)
}
}
}
pub fn set(&mut self, hash: u64, depth: Depth, score: Score, best_move: PieceMove, bound: Bound) {
let age = self.age.load(Ordering::Acquire);
let h = self.entries.get();
let n = self.len() as u64;
let k = (hash % n) as usize;
if age != h[k].age() || depth >= h[k].depth() {
h[k].store(hash, depth, score, best_move, bound, age);
self.stats_inserts += 1;
}
}
pub fn reset(&mut self) {
let old_age = self.age.load(Ordering::Relaxed);
let new_age = if old_age >= u8::MAX - 1 { 0 } else { old_age + 1 };
self.age.store(new_age, Ordering::Release);
self.clear_stats();
}
pub fn clear(&mut self) {
let n = self.len();
self.entries = Arc::new(SharedTable::with_capacity(n));
self.age.store(0, Ordering::Release);
self.clear_stats();
}
fn clear_stats(&mut self) {
self.stats_lookups = 0;
self.stats_inserts = 0;
self.stats_hits = 0;
self.stats_collisions = 0;
}
pub fn len(&self) -> usize {
self.entries.get().len()
}
pub fn memory(&self) -> usize {
self.len() * mem::size_of::<Transposition>()
}
#[cfg(feature = "std")]
pub fn print_stats(&mut self) {
let v = self.memory() as u64;
let z = (63 - v.leading_zeros()) / 10;
let size = v / (1 << (10 * z));
let unit = "KMG".chars().nth((z - 1) as usize).unwrap();
let mut exact_count = 0;
let mut upper_count = 0;
let mut lower_count = 0;
for t in self.entries.get() {
if t.best_move().is_null() {
continue;
}
match t.bound() {
Bound::Exact => exact_count += 1,
Bound::Upper => upper_count += 1,
Bound::Lower => lower_count += 1,
}
}
let collisions = self.stats_collisions;
let hits = self.stats_hits;
let miss = self.stats_lookups - hits - collisions;
println!("# {:15} {:>8} ({} {}B)", "tt size:", self.len(), size, unit);
let percent = (lower_count as f64) * 100.0 / (self.len() as f64);
println!("# {:15} {:>8} ({:.2} %)", " - lower:", lower_count, percent);
let percent = (upper_count as f64) * 100.0 / (self.len() as f64);
println!("# {:15} {:>8} ({:.2} %)", " - upper:", upper_count, percent);
let percent = (exact_count as f64) * 100.0 / (self.len() as f64);
println!("# {:15} {:>8} ({:.2} %)", " - exact:", exact_count, percent);
println!("# {:15} {:>8}", "tt inserts:", self.stats_inserts);
println!("# {:15} {:>8}", "tt lookups:", self.stats_lookups);
let percent = (miss as f64) * 100.0 / (self.stats_lookups as f64);
println!("# {:15} {:>8} ({:.2} %)", " - miss:", miss, percent);
let percent = (hits as f64) * 100.0 / (self.stats_lookups as f64);
println!("# {:15} {:>8} ({:.2} %)", " - hits:", hits, percent);
let percent = (collisions as f64) * 100.0 / (self.stats_lookups as f64);
println!("# {:15} {:>8} ({:.2} %)", " - collisions:", collisions, percent);
}
}
pub struct SharedTable {
inner: Box<[Transposition]>,
}
impl SharedTable {
pub fn with_capacity(capacity: usize) -> SharedTable {
SharedTable {
inner: vec![Transposition::new_null(); capacity].into_boxed_slice()
}
}
pub fn get(&self) -> &[Transposition] {
&self.inner
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "std")]
use std::sync::{Arc, Barrier};
#[cfg(feature = "std")]
use std::thread;
use super::*;
use crate::square::*;
use crate::piece_move::PieceMove;
#[test]
fn test_transposition_table_size() {
assert_eq!(TranspositionTable::with_memory(512).len(), 32); assert_eq!(TranspositionTable::with_capacity(32).len(), 32);
assert_eq!(TranspositionTable::with_capacity(24).len(), 32);
assert_eq!(TranspositionTable::with_memory(16 << 20).len(), 1048576);
}
#[test]
fn test_transposition_table() {
let mut tt = TranspositionTable::with_capacity(1 << 20);
let h = 42;
let m = PieceMove::new(E2, E4, DOUBLE_PAWN_PUSH);
let s = 100;
let d = 8;
let b = Bound::Exact;
let a = 0;
let t = Transposition::new(h, d, s, m, b, a);
assert_eq!(t.best_move(), m);
assert_eq!(t.score(), s);
assert_eq!(t.depth(), d);
tt.set(h, d, s, m, b);
assert_eq!(tt.get(h).unwrap().best_move(), m);
let h = 1337;
assert_eq!(tt.get(h), None);
}
#[cfg(feature = "std")]
#[test]
fn test_transposition_table_in_threads() {
let h = 42;
let m = PieceMove::new(E2, E4, DOUBLE_PAWN_PUSH);
let s = 100;
let d = 8;
let b = Bound::Exact;
let n = 4;
let mut children = Vec::with_capacity(n);
let shared_tt = TranspositionTable::with_memory(1 << 20);
let barrier = Arc::new(Barrier::new(n));
for i in 0..n {
let mut tt = shared_tt.clone();
let c = barrier.clone();
children.push(thread::spawn(move || {
if i == 0 {
tt.set(h, d, s, m, b); }
c.wait(); tt.get(h).unwrap().best_move() }));
}
for child in children {
assert_eq!(child.join().unwrap(), m);
}
}
}