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:
Bruno BELANYI 2024-04-01 11:39:29 +01:00
parent 9dea85054d
commit 52772167a6
2 changed files with 248 additions and 13 deletions

View file

@ -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) }
})