Pre-generate the magic bitboard seeds
My naive RNG implementation takes about ~40 seconds to generate the magic bitboards for both bishops and rooks (or ~1 second in release mode). If we pre-generate the seeds, we can instead make it ~instantaneous. The self-modifying code is inspired by matklad [1]. [1]: https://matklad.github.io/2022/03/26/self-modifying-code.html
This commit is contained in:
parent
9dea85054d
commit
52772167a6
|
@ -4,25 +4,35 @@ use crate::{
|
|||
board::{Bitboard, Color, File, Square},
|
||||
movegen::{
|
||||
naive,
|
||||
wizardry::{generate_bishop_magics, generate_rook_magics, MagicMoves, RandGen},
|
||||
wizardry::{
|
||||
generate_bishop_magics, generate_rook_magics, MagicMoves, RandGen, BISHOP_SEED,
|
||||
ROOK_SEED,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// A simple XOR-shift RNG implementation.
|
||||
struct SimpleRng(u64);
|
||||
// A pre-rolled RNG for magic bitboard generation, using pre-determined values.
|
||||
struct PreRolledRng {
|
||||
numbers: [u64; 64],
|
||||
current_index: usize,
|
||||
}
|
||||
|
||||
impl SimpleRng {
|
||||
pub fn new() -> Self {
|
||||
Self(4) // https://xkcd.com/221/
|
||||
impl PreRolledRng {
|
||||
pub fn new(numbers: [u64; 64]) -> Self {
|
||||
Self {
|
||||
numbers,
|
||||
current_index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RandGen for SimpleRng {
|
||||
impl RandGen for PreRolledRng {
|
||||
fn gen(&mut self) -> u64 {
|
||||
self.0 ^= self.0 >> 12;
|
||||
self.0 ^= self.0 << 25;
|
||||
self.0 ^= self.0 >> 27;
|
||||
self.0
|
||||
// We roll 3 numbers per square to bitwise-and them together.
|
||||
// Just return the same one 3 times as a work-around.
|
||||
let res = self.numbers[self.current_index / 3];
|
||||
self.current_index += 1;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +96,7 @@ pub fn bishop_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
|||
static BISHOP_MAGICS: OnceLock<MagicMoves> = OnceLock::new();
|
||||
BISHOP_MAGICS
|
||||
.get_or_init(|| {
|
||||
let (magics, moves) = generate_bishop_magics(&mut SimpleRng::new());
|
||||
let (magics, moves) = generate_bishop_magics(&mut PreRolledRng::new(BISHOP_SEED));
|
||||
// SAFETY: we used the generator function to compute these values
|
||||
unsafe { MagicMoves::new(magics, moves) }
|
||||
})
|
||||
|
@ -98,7 +108,7 @@ pub fn rook_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
|||
static ROOK_MAGICS: OnceLock<MagicMoves> = OnceLock::new();
|
||||
ROOK_MAGICS
|
||||
.get_or_init(|| {
|
||||
let (magics, moves) = generate_rook_magics(&mut SimpleRng::new());
|
||||
let (magics, moves) = generate_rook_magics(&mut PreRolledRng::new(ROOK_SEED));
|
||||
// SAFETY: we used the generator function to compute these values
|
||||
unsafe { MagicMoves::new(magics, moves) }
|
||||
})
|
||||
|
|
|
@ -56,3 +56,228 @@ impl MagicMoves {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region:sourcegen
|
||||
/// A set of magic numbers for bishop move generation.
|
||||
pub(crate) const BISHOP_SEED: [u64; 64] = [
|
||||
4908958787341189172,
|
||||
1157496606860279808,
|
||||
289395876198088778,
|
||||
649648646467355137,
|
||||
19162426089930848,
|
||||
564067194896448,
|
||||
18586170375029026,
|
||||
9185354800693760,
|
||||
72172012436987968,
|
||||
317226351607872,
|
||||
2597178509285688384,
|
||||
1162205282238464,
|
||||
144154788211329152,
|
||||
172197832046936160,
|
||||
4625762105940000802,
|
||||
1477217245166903296,
|
||||
2251937789583872,
|
||||
289373902621379585,
|
||||
4616200855845409024,
|
||||
2251909637357568,
|
||||
3532510975437640064,
|
||||
563517968228352,
|
||||
562953309660434,
|
||||
1196005458310201856,
|
||||
2350914225914520576,
|
||||
2287018679861376,
|
||||
13836188353273790593,
|
||||
11267795163676832,
|
||||
297519119119499264,
|
||||
18588344158519552,
|
||||
10453428171813953792,
|
||||
72128237668534272,
|
||||
1298164929055953920,
|
||||
865575144395900952,
|
||||
9293076573325312,
|
||||
108104018148197376,
|
||||
578503662094123152,
|
||||
4665870505495102224,
|
||||
6066493872259301520,
|
||||
285877477613857,
|
||||
2328941618281318466,
|
||||
721165292771739652,
|
||||
4899973577790523400,
|
||||
75050392749184,
|
||||
2305878200632215680,
|
||||
11530099074925593616,
|
||||
290561512873919880,
|
||||
18652187227888000,
|
||||
3379933716168704,
|
||||
9223409493537718272,
|
||||
22273835729926,
|
||||
1152921524003672064,
|
||||
4647812741240848385,
|
||||
1244225087719112712,
|
||||
7367907171013001728,
|
||||
9263922034316951570,
|
||||
300758214358598160,
|
||||
4611686331973636096,
|
||||
2377900605806479360,
|
||||
6958097192913601024,
|
||||
864691130877743617,
|
||||
703824948904066,
|
||||
612700674899317536,
|
||||
180742128018784384,
|
||||
];
|
||||
|
||||
/// A set of magic numbers for rook move generation.
|
||||
pub(crate) const ROOK_SEED: [u64; 64] = [
|
||||
2341871943948451840,
|
||||
18015635528220736,
|
||||
72066665545773824,
|
||||
1188959097794342912,
|
||||
12141713393631625314,
|
||||
720649693658353672,
|
||||
36029896538981888,
|
||||
36033359356363520,
|
||||
140746619355268,
|
||||
1158339898446446661,
|
||||
36591886560003650,
|
||||
578853633228023808,
|
||||
2392554490300416,
|
||||
140814806160384,
|
||||
180706952366596608,
|
||||
10696087878779396,
|
||||
1153260703948210820,
|
||||
310748649170673678,
|
||||
36311372044308544,
|
||||
9223444604757615104,
|
||||
1267187285230592,
|
||||
282574622818306,
|
||||
18722484274726152,
|
||||
2271591090110593,
|
||||
1153063519847989248,
|
||||
10168327557107712,
|
||||
4507998211276833,
|
||||
1153203035420233728,
|
||||
4631961017139660032,
|
||||
2454499182462107776,
|
||||
289367288355753288,
|
||||
18015815850820609,
|
||||
9268726066908758912,
|
||||
11547264697673728000,
|
||||
2314929519368081536,
|
||||
140943655192577,
|
||||
20266215511427202,
|
||||
180706969441535248,
|
||||
1302683805944911874,
|
||||
11534000122299940994,
|
||||
22676602724843520,
|
||||
4639271120198041668,
|
||||
1302104069046927376,
|
||||
9184220895313928,
|
||||
4612249105954373649,
|
||||
562984581726212,
|
||||
2312678200579457040,
|
||||
4647736876550193157,
|
||||
3170604524138139776,
|
||||
4684447574787096704,
|
||||
20283792725901696,
|
||||
1152992019380963840,
|
||||
117383863558471808,
|
||||
1153488854922068096,
|
||||
17596884583424,
|
||||
90074759127192064,
|
||||
4900502436426416706,
|
||||
4573968656793901,
|
||||
1161084564408385,
|
||||
1657887889314811910,
|
||||
4614501455660058690,
|
||||
4612530729109422081,
|
||||
642458506527236,
|
||||
1116704154754,
|
||||
];
|
||||
// endregion:sourcegen
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
// A simple XOR-shift RNG implementation.
|
||||
struct SimpleRng(u64);
|
||||
|
||||
impl SimpleRng {
|
||||
pub fn new() -> Self {
|
||||
Self(4) // https://xkcd.com/221/
|
||||
}
|
||||
}
|
||||
|
||||
impl RandGen for SimpleRng {
|
||||
fn gen(&mut self) -> u64 {
|
||||
self.0 ^= self.0 >> 12;
|
||||
self.0 ^= self.0 << 25;
|
||||
self.0 ^= self.0 >> 27;
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rng() {
|
||||
let mut rng = SimpleRng::new();
|
||||
|
||||
assert_eq!(rng.gen(), 134217733);
|
||||
assert_eq!(rng.gen(), 4504699139039237);
|
||||
assert_eq!(rng.gen(), 13512173405898766);
|
||||
assert_eq!(rng.gen(), 9225626310854853124);
|
||||
assert_eq!(rng.gen(), 29836777971867270);
|
||||
}
|
||||
|
||||
fn split_twice<'a>(
|
||||
text: &'a str,
|
||||
start_marker: &str,
|
||||
end_marker: &str,
|
||||
) -> Option<(&'a str, &'a str, &'a str)> {
|
||||
let (prefix, rest) = text.split_once(start_marker)?;
|
||||
let (mid, suffix) = rest.split_once(end_marker)?;
|
||||
Some((prefix, mid, suffix))
|
||||
}
|
||||
|
||||
fn array_string(piece_type: &str, values: &[Magic]) -> String {
|
||||
let mut res = format!(
|
||||
"/// A set of magic numbers for {} move generation.\n",
|
||||
piece_type
|
||||
);
|
||||
res.push_str(&format!(
|
||||
"pub(crate) const {}_SEED: [u64; 64] = [\n",
|
||||
piece_type.to_uppercase()
|
||||
));
|
||||
for magic in values {
|
||||
res.push_str(&format!(" {},\n", magic.magic));
|
||||
}
|
||||
res.push_str("];\n");
|
||||
res
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "slow"]
|
||||
// Regenerates the magic bitboard numbers.
|
||||
fn regen_magic_seeds() {
|
||||
// We only care about the magics, the moves can be recomputed at runtime ~cheaply.
|
||||
let (bishop_magics, _) = generate_bishop_magics(&mut SimpleRng::new());
|
||||
let (rook_magics, _) = generate_rook_magics(&mut SimpleRng::new());
|
||||
|
||||
let original_text = std::fs::read_to_string(file!()).unwrap();
|
||||
|
||||
let bishop_array = array_string("bishop", &bishop_magics[..]);
|
||||
let rook_array = array_string("rook", &rook_magics[..]);
|
||||
|
||||
let new_text = {
|
||||
let start_marker = "// region:sourcegen\n";
|
||||
let end_marker = "// endregion:sourcegen\n";
|
||||
let (prefix, _, suffix) =
|
||||
split_twice(&original_text, start_marker, end_marker).unwrap();
|
||||
format!("{prefix}{start_marker}{bishop_array}\n{rook_array}{end_marker}{suffix}")
|
||||
};
|
||||
|
||||
if new_text != original_text {
|
||||
std::fs::write(file!(), new_text).unwrap();
|
||||
panic!("source was not up-to-date")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue