use super::job::{absorb, passes, seed, Job};
use hemera::field::Goldilocks;
use hemera::permutation::permute_batch;
use std::sync::atomic::{AtomicU32, Ordering::Relaxed};
pub const BATCH: usize = 256;
#[derive(Clone, Copy, Debug)]
pub struct Cpu {
pub threads: usize,
}
impl Cpu {
pub fn new(threads: usize) -> Self {
let t = if threads == 0 {
std::thread::available_parallelism().map_or(1, |n| n.get())
} else {
threads
};
Cpu { threads: t }
}
}
fn scan(job: &Job, base: u128, n: usize) -> Option<usize> {
let mut st: Vec<[Goldilocks; 16]> = (0..n as u128).map(|i| seed(job, base + i)).collect();
permute_batch(&mut st);
for b in 1..4 {
for s in st.iter_mut() {
absorb(job, s, b);
}
permute_batch(&mut st);
}
st.iter().position(|s| passes(job, s))
}
impl super::Backend for Cpu {
fn name(&self) -> String {
format!("cpu ({} threads)", self.threads)
}
fn search(&self, job: &Job, base: u128, count: u32) -> Result<Option<u32>, String> {
let count = count as usize;
if count == 0 {
return Ok(None);
}
let best = AtomicU32::new(u32::MAX);
let per = count.div_ceil(self.threads).max(BATCH);
std::thread::scope(|s| {
for t in 0..self.threads {
let from = t * per;
if from >= count {
break;
}
let to = (from + per).min(count);
let best = &best;
s.spawn(move || {
let mut at = from;
while at < to && (at as u32) < best.load(Relaxed) {
let n = BATCH.min(to - at);
if let Some(i) = scan(job, base + at as u128, n) {
best.fetch_min((at + i) as u32, Relaxed);
return;
}
at += n;
}
});
}
});
let b = best.load(Relaxed);
Ok((b != u32::MAX).then_some(b))
}
fn dispatch(&self) -> u32 {
(self.threads * BATCH * 64) as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chain::mine::target::{pow2, verifies};
use crate::chain::mine::Backend;
#[test]
fn cpu_finds_the_least_passing_nonce_across_threads() {
let header = [0x5au8; 168];
let job = Job::new(&header, pow2(244));
let cpu = Cpu::new(4);
let base = (7u128 << 64) + 12_345;
let i = cpu
.search(&job, base, 1 << 15)
.expect("ok")
.expect("a nonce");
let one = Cpu::new(1);
assert_eq!(one.search(&job, base, 1 << 15).expect("ok"), Some(i));
assert!(verifies(&job.header_with(base + i as u128), &job.target));
for k in 0..i {
assert!(
!verifies(&job.header_with(base + k as u128), &job.target),
"{k}"
);
}
}
#[test]
fn cpu_reports_none() {
let job = Job::new(&[0x11u8; 168], [1, 0, 0, 0]);
assert_eq!(Cpu::new(2).search(&job, 0, 4096).expect("ok"), None);
}
}