From 76923bb6fef2982dbce201227f6a33788390ce35 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 5 Oct 2024 16:18:21 -0700 Subject: [PATCH] Optimize magics Reduce the size of the Magics table by half on modern cpu's and lay it out to match our access pattern. Namely we typically access the magics for the same square for both bishop and rook back to back so we want those to be in the same cache line. https://tests.stockfishchess.org/tests/view/6701c9b386d5ee47d953bcf4 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 121664 W: 31931 L: 31497 D: 58236 Ptnml(0-2): 395, 13658, 32322, 14032, 425 A similar patch minus the size reduction finished yellow https://tests.stockfishchess.org/tests/view/6695f03f4ff211be9d4ec16c LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 310688 W: 80940 L: 80746 D: 149002 Ptnml(0-2): 1119, 35032, 82846, 35230, 1117 closes https://github.com/official-stockfish/Stockfish/pull/5623 No functional change --- src/bitboard.cpp | 40 +++++++++++++++++++++++----------------- src/bitboard.h | 21 ++++++++++++--------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index a8b4e5f4..deda6da2 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -34,15 +34,14 @@ Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; -Magic RookMagics[SQUARE_NB]; -Magic BishopMagics[SQUARE_NB]; +alignas(64) Magic Magics[SQUARE_NB][2]; namespace { Bitboard RookTable[0x19000]; // To store rook attacks Bitboard BishopTable[0x1480]; // To store bishop attacks -void init_magics(PieceType pt, Bitboard table[], Magic magics[]); +void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]); // Returns the bitboard of target square for the given step // from the given square. If the step is off the board, returns empty bitboard. @@ -82,8 +81,8 @@ void Bitboards::init() { for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); - init_magics(ROOK, RookTable, RookMagics); - init_magics(BISHOP, BishopTable, BishopMagics); + init_magics(ROOK, RookTable, Magics); + init_magics(BISHOP, BishopTable, Magics); for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { @@ -142,39 +141,47 @@ Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { // bitboards are used to look up attacks of sliding pieces. As a reference see // https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use // the so called "fancy" approach. -void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { +void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { +#ifndef USE_PEXT // Optimal PRNG seeds to pick the correct magics in the shortest time int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020}, {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}}; - Bitboard occupancy[4096], reference[4096], edges, b; - int epoch[4096] = {}, cnt = 0, size = 0; + Bitboard occupancy[4096]; + int epoch[4096] = {}, cnt = 0; +#endif + Bitboard reference[4096]; + int size = 0; for (Square s = SQ_A1; s <= SQ_H8; ++s) { // Board edges are not considered in the relevant occupancies - edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s)); + Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s)); // Given a square 's', the mask is the bitboard of sliding attacks from // 's' computed on an empty board. The index must be big enough to contain // all the attacks for each possible subset of the mask and so is 2 power // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. - Magic& m = magics[s]; + Magic& m = magics[s][pt - BISHOP]; m.mask = sliding_attack(pt, s, 0) & ~edges; - m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); - +#ifndef USE_PEXT + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); +#endif // Set the offset for the attacks table of the square. We have individual // table sizes for each square with "Fancy Magic Bitboards". - m.attacks = s == SQ_A1 ? table : magics[s - 1].attacks + size; + m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size; + size = 0; // Use Carry-Rippler trick to enumerate all subsets of masks[s] and // store the corresponding sliding attack bitboard in reference[]. - b = size = 0; + Bitboard b = 0; do { +#ifndef USE_PEXT occupancy[size] = b; +#endif reference[size] = sliding_attack(pt, s, b); if (HasPext) @@ -184,9 +191,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { b = (b - m.mask) & m.mask; } while (b); - if (HasPext) - continue; - +#ifndef USE_PEXT PRNG rng(seeds[Is64Bit][rank_of(s)]); // Find a magic for square 's' picking up an (almost) random number @@ -215,6 +220,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { break; } } +#endif } } } diff --git a/src/bitboard.h b/src/bitboard.h index cdff4c75..c4bf18b5 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -67,27 +67,31 @@ extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; // Magic holds all magic bitboards relevant data for a single square struct Magic { Bitboard mask; - Bitboard magic; Bitboard* attacks; - unsigned shift; +#ifndef USE_PEXT + Bitboard magic; + unsigned shift; +#endif // Compute the attack's index using the 'magic bitboards' approach unsigned index(Bitboard occupied) const { - if (HasPext) - return unsigned(pext(occupied, mask)); - +#ifdef USE_PEXT + return unsigned(pext(occupied, mask)); +#else if (Is64Bit) return unsigned(((occupied & mask) * magic) >> shift); unsigned lo = unsigned(occupied) & unsigned(mask); unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; +#endif } + + Bitboard attacks_bb(Bitboard occupied) const { return attacks[index(occupied)]; } }; -extern Magic RookMagics[SQUARE_NB]; -extern Magic BishopMagics[SQUARE_NB]; +extern Magic Magics[SQUARE_NB][2]; constexpr Bitboard square_bb(Square s) { assert(is_ok(s)); @@ -229,9 +233,8 @@ inline Bitboard attacks_bb(Square s, Bitboard occupied) { switch (Pt) { case BISHOP : - return BishopMagics[s].attacks[BishopMagics[s].index(occupied)]; case ROOK : - return RookMagics[s].attacks[RookMagics[s].index(occupied)]; + return Magics[s][Pt - BISHOP].attacks_bb(occupied); case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); default :