Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Reckless supports the following UCI options:
| UCI_Chess960 | false | Enable Chess960 (Fischer Random) support [false–true] |
| Minimal | false | Enable minimal UCI output [false–true] |
| MoveOverhead | 100 | Time in milliseconds reserved for overhead during each move [0–2000] |
| Ponder | false | Enable pondering support for `go ponder` [false–true] |
| Clear Hash | — | Clear the transposition table |
| SyzygyPath | — | Path to Syzygy endgame tablebases |

Expand Down
7 changes: 7 additions & 0 deletions src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ pub fn start(td: &mut ThreadData, report: Report, thread_count: usize) {

// Iterative Deepening
for depth in 1..MAX_PLY as i32 {
if td.time_manager.is_ponder() && td.shared.ponderhit.swap(false, Ordering::AcqRel) {
td.time_manager.on_ponderhit();
td.shared.pondering.store(false, Ordering::Release);
soft_stop_voted = false;
td.shared.soft_stop_votes.store(0, Ordering::Release);
}

if td.id == 0
&& let Limits::Depth(maximum) = td.time_manager.limits()
&& depth > maximum
Expand Down
8 changes: 6 additions & 2 deletions src/thread.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::{
Arc,
atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering},
atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering},
};

use crate::{
Expand Down Expand Up @@ -97,6 +97,8 @@ unsafe impl NumaValue for SharedCorrectionHistory {}
pub struct SharedContext {
pub tt: TranspositionTable,
pub status: Status,
pub pondering: AtomicBool,
pub ponderhit: AtomicBool,
pub nodes: Counter,
pub tb_hits: Counter,
pub soft_stop_votes: AtomicUsize,
Expand All @@ -112,6 +114,8 @@ impl Default for SharedContext {
Self {
tt: TranspositionTable::default(),
status: Status::default(),
pondering: AtomicBool::new(false),
ponderhit: AtomicBool::new(false),
nodes: Counter::default(),
tb_hits: Counter::default(),
soft_stop_votes: AtomicUsize::new(0),
Expand Down Expand Up @@ -160,7 +164,7 @@ impl ThreadData {
id: 0,
shared,
board: Board::starting_position(),
time_manager: TimeManager::new(Limits::Infinite, 0, 0),
time_manager: TimeManager::new(Limits::Infinite, 0, 0, false),
stack: Stack::new(),
nnue: Network::default(),
root_moves: Vec::new(),
Expand Down
6 changes: 5 additions & 1 deletion src/threadpool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,15 @@ impl ThreadPool {
self.vector = make_thread_data(shared, &self.workers, Board::starting_position().into());
}

pub fn execute_searches(&mut self, time_manager: TimeManager, report: Report, shared: &Arc<SharedContext>) {
pub fn execute_searches(
&mut self, time_manager: TimeManager, report: Report, shared: &Arc<SharedContext>, pondering: bool,
) {
shared.tt.increment_age();

shared.nodes.reset();
shared.tb_hits.reset();
shared.pondering.store(pondering, Ordering::Release);
shared.ponderhit.store(false, Ordering::Release);
shared.soft_stop_votes.store(0, Ordering::Release);
shared.status.set(Status::RUNNING);
shared.best_stats.iter().for_each(|x| {
Expand Down
63 changes: 62 additions & 1 deletion src/time.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};

use crate::thread::ThreadData;
Expand All @@ -17,13 +18,56 @@ const TIME_OVERHEAD_MS: u64 = 15;
#[derive(Clone)]
pub struct TimeManager {
limits: Limits,
ponder: bool,
start_time: Instant,
soft_bound: Duration,
hard_bound: Duration,
}

#[cfg(test)]
mod tests {
use std::sync::Arc;

use crate::thread::SharedContext;

use super::*;

#[test]
fn test_ponderhit_turns_off_pondering_in_timer() {
let mut tm = TimeManager::new(Limits::Time(10_000), 0, 0, true);
assert!(tm.is_ponder());
tm.on_ponderhit();
assert!(!tm.is_ponder());
}

#[test]
fn test_ponderhit_preserves_elapsed_time() {
let mut tm = TimeManager::new(Limits::Time(10_000), 0, 0, true);
std::thread::sleep(Duration::from_millis(10));
let before = tm.elapsed();
tm.on_ponderhit();
let after = tm.elapsed();
assert!(after >= before);
}

#[test]
fn test_soft_limit_ignored_while_pondering() {
let shared = Arc::new(SharedContext::default());
let mut td = crate::thread::ThreadData::new(shared.clone());
td.time_manager = TimeManager::new(Limits::Time(1), 0, 0, true);
td.completed_depth = 1;

shared.pondering.store(true, Ordering::Release);
std::thread::sleep(Duration::from_millis(20));
assert!(!td.time_manager.soft_limit(&td, || 1.0));

shared.pondering.store(false, Ordering::Release);
assert!(td.time_manager.soft_limit(&td, || 1.0));
}
}

impl TimeManager {
pub fn new(limits: Limits, fullmove_number: usize, move_overhead: u64) -> Self {
pub fn new(limits: Limits, fullmove_number: usize, move_overhead: u64, ponder: bool) -> Self {
let soft;
let hard;

Expand Down Expand Up @@ -57,6 +101,7 @@ impl TimeManager {

Self {
limits,
ponder,
start_time: Instant::now(),
soft_bound: Duration::from_millis(soft.saturating_sub(TIME_OVERHEAD_MS)),
hard_bound: Duration::from_millis(hard.saturating_sub(TIME_OVERHEAD_MS)),
Expand All @@ -68,6 +113,10 @@ impl TimeManager {
}

pub fn soft_limit(&self, td: &ThreadData, multiplier: impl Fn() -> f32) -> bool {
if self.ponder && td.shared.pondering.load(Ordering::Acquire) {
return false;
}

match self.limits {
Limits::Infinite | Limits::Depth(_) => false,
Limits::Nodes(maximum) => td.shared.nodes.aggregate() >= maximum,
Expand All @@ -81,6 +130,10 @@ impl TimeManager {
return false;
}

if self.ponder && td.shared.pondering.load(Ordering::Acquire) {
return false;
}

match self.limits {
Limits::Infinite | Limits::Depth(_) => false,
Limits::Nodes(maximum) => td.shared.nodes.aggregate() > maximum,
Expand All @@ -91,4 +144,12 @@ impl TimeManager {
pub fn limits(&self) -> Limits {
self.limits.clone()
}

pub fn is_ponder(&self) -> bool {
self.ponder
}

pub fn on_ponderhit(&mut self) {
self.ponder = false;
}
}
4 changes: 2 additions & 2 deletions src/tools/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ pub fn bench<const PRETTY: bool>(args: &[&str]) {
let now = Instant::now();

let board = Board::from_fen(position).unwrap();
let time_manager = TimeManager::new(Limits::Depth(depth), 0, 0);
let time_manager = TimeManager::new(Limits::Depth(depth), 0, 0, false);

for td in &mut pool.vector {
td.board = board.clone();
}

pool.execute_searches(time_manager, Report::None, &shared);
pool.execute_searches(time_manager, Report::None, &shared, false);

nodes += shared.nodes.aggregate();

Expand Down
Loading