mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 12:47:08 +00:00
Hyperbola quintessence for ARM
Passed STC https://tests.stockfishchess.org/tests/view/69f44c1e1e5788938e86aa2a LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 27680 W: 7340 L: 7053 D: 13287 Ptnml(0-2): 72, 2862, 7683, 3153, 70 x86 is unaffected Data from Joost's machine (Neoverse V2): ``` ==== master ==== ==== Bench: 2723949 ==== 1 Nodes/second : 276819933 2 Nodes/second : 273971948 3 Nodes/second : 275567995 Average (over 3): 275453292 ==== 5b54755db23a3de29407b805b85e7bbdef28df49 ==== ==== Bench: 2723949 ==== 1 Nodes/second : 282861542 2 Nodes/second : 279085277 3 Nodes/second : 279612223 Average (over 3): 280519680 (+1.8%) ``` Data from my M1 macbook ``` Result of 100 runs ================== base (./stockfish ) = 1474702 +/- 6169 test (...fish.rbit-pr) = 1500236 +/- 6690 diff = +25533 +/- 4011 speedup = +0.0173 P(speedup > 0) = 1.0000 ``` Hyperbola quintessence works nicely on ARM because it has an efficient bit reversal instruction `rbit`. This lets us avoid the large magic bitboards lookup table. Codegen looks nice (this is for rook attacks I think): ``` 0000000100001ecc <__ZN9Stockfish10attacks_bbILNS_9PieceTypeE3EEEyNS_6SquareEy>: 100001ecc: 2a0003e8 mov w8, w0 100001ed0: f002cb09 adrp x9, 0x105964000 <dyld_stub_binder+0x105964000> 100001ed4: 91200129 add x9, x9, #0x800 100001ed8: 8b081928 add x8, x9, x8, lsl #6 100001edc: a9402909 ldp x9, x10, [x8] 100001ee0: 8a01012b and x11, x9, x1 100001ee4: dac0016c rbit x12, x11 100001ee8: a941210d ldp x13, x8, [x8, #0x10] 100001eec: cb08018c sub x12, x12, x8 100001ef0: dac0018c rbit x12, x12 100001ef4: cb0d016b sub x11, x11, x13 100001ef8: ca0b018b eor x11, x12, x11 100001efc: 8a090169 and x9, x11, x9 100001f00: 8a01014b and x11, x10, x1 100001f04: dac0016c rbit x12, x11 100001f08: cb080188 sub x8, x12, x8 100001f0c: dac00108 rbit x8, x8 100001f10: cb0d016b sub x11, x11, x13 100001f14: ca0b0108 eor x8, x8, x11 100001f18: 8a0a0108 and x8, x8, x10 100001f1c: 8b090100 add x0, x8, x9 100001f20: d65f03c0 ret ``` closes https://github.com/official-stockfish/Stockfish/pull/6795 No functional change
This commit is contained in:
committed by
Joost VandeVondele
parent
fe1d2b456c
commit
dc1686345c
+59
-21
@@ -62,6 +62,39 @@ std::string Bitboards::pretty(Bitboard b) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_HYPERBOLA_QUINT
|
||||||
|
static Bitboard line_mask(Square sq, Direction d1, Direction d2) {
|
||||||
|
Bitboard mask = 0, dest;
|
||||||
|
for (Direction d : {d1, d2})
|
||||||
|
{
|
||||||
|
Square s = sq;
|
||||||
|
while ((dest = Bitboards::safe_destination(s, d)))
|
||||||
|
{
|
||||||
|
mask |= dest;
|
||||||
|
s += d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_magics(Magic magics[][2]) {
|
||||||
|
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||||
|
{
|
||||||
|
Magic& rook = magics[s][ROOK - BISHOP];
|
||||||
|
rook.mask1 = line_mask(s, NORTH, SOUTH);
|
||||||
|
rook.mask2 = line_mask(s, EAST, WEST);
|
||||||
|
|
||||||
|
Magic& bishop = magics[s][BISHOP - BISHOP];
|
||||||
|
bishop.mask1 = line_mask(s, NORTH_EAST, SOUTH_WEST);
|
||||||
|
bishop.mask2 = line_mask(s, NORTH_WEST, SOUTH_EAST);
|
||||||
|
|
||||||
|
rook.r = bishop.r = square_bb(s) * 2;
|
||||||
|
rook.rr = bishop.rr = square_bb(Square(63 - int(s))) * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
[[maybe_unused]] constexpr Bitboard constexpr_pext(Bitboard b, Bitboard m) {
|
[[maybe_unused]] constexpr Bitboard constexpr_pext(Bitboard b, Bitboard m) {
|
||||||
Bitboard result = 0, bit = 0;
|
Bitboard result = 0, bit = 0;
|
||||||
@@ -74,23 +107,23 @@ namespace {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes all rook and bishop attacks at startup or optionally, compile time. Magic
|
// Computes all rook and bishop attacks at startup or optionally, compile time. Magic
|
||||||
// bitboards are used to look up attacks of sliding pieces. As a reference see
|
// 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
|
// https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use
|
||||||
// the so called "fancy" approach.
|
// the so called "fancy" approach.
|
||||||
#ifdef USE_COMPTIME_ATTACKS
|
#ifdef USE_COMPTIME_ATTACKS
|
||||||
constexpr
|
constexpr
|
||||||
#endif
|
#endif
|
||||||
void
|
void
|
||||||
init_magics(PieceType pt,
|
init_magics(PieceType pt,
|
||||||
MagicMask table[],
|
MagicMask table[],
|
||||||
Magic magics[][2],
|
Magic magics[][2],
|
||||||
[[maybe_unused]] bool tableAlreadyInit) {
|
[[maybe_unused]] bool tableAlreadyInit) {
|
||||||
#if !defined(USE_COMPTIME_ATTACKS)
|
#if !defined(USE_COMPTIME_ATTACKS)
|
||||||
tableAlreadyInit = false;
|
tableAlreadyInit = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef USE_PEXT
|
#ifndef USE_PEXT
|
||||||
// Optimal PRNG seeds to pick the correct magics in the shortest time
|
// Optimal PRNG seeds to pick the correct magics in the shortest time
|
||||||
int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
|
int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
|
||||||
{728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
|
{728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
|
||||||
@@ -98,7 +131,7 @@ constexpr
|
|||||||
Bitboard occupancy[4096];
|
Bitboard occupancy[4096];
|
||||||
int epoch[4096] = {}, cnt = 0;
|
int epoch[4096] = {}, cnt = 0;
|
||||||
Bitboard reference[4096] = {};
|
Bitboard reference[4096] = {};
|
||||||
#endif
|
#endif
|
||||||
int size = 0;
|
int size = 0;
|
||||||
|
|
||||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||||
@@ -114,11 +147,11 @@ constexpr
|
|||||||
Magic& m = magics[s][pt - BISHOP];
|
Magic& m = magics[s][pt - BISHOP];
|
||||||
Bitboard attacks = Bitboards::sliding_attack(pt, s, 0);
|
Bitboard attacks = Bitboards::sliding_attack(pt, s, 0);
|
||||||
m.mask = attacks & ~edges;
|
m.mask = attacks & ~edges;
|
||||||
#ifdef USE_PEXT
|
#ifdef USE_PEXT
|
||||||
m.pseudoAttacks = attacks;
|
m.pseudoAttacks = attacks;
|
||||||
#else
|
#else
|
||||||
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
|
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
|
||||||
#endif
|
#endif
|
||||||
// Set the offset for the attacks table of the square. We have individual
|
// Set the offset for the attacks table of the square. We have individual
|
||||||
// table sizes for each square with "Fancy Magic Bitboards".
|
// table sizes for each square with "Fancy Magic Bitboards".
|
||||||
m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size;
|
m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size;
|
||||||
@@ -130,7 +163,7 @@ constexpr
|
|||||||
[[maybe_unused]] Bitboard prevSliding = -1;
|
[[maybe_unused]] Bitboard prevSliding = -1;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
#ifdef USE_PEXT
|
#ifdef USE_PEXT
|
||||||
if (!tableAlreadyInit)
|
if (!tableAlreadyInit)
|
||||||
{
|
{
|
||||||
Bitboard sliding = Bitboards::sliding_attack(pt, s, b);
|
Bitboard sliding = Bitboards::sliding_attack(pt, s, b);
|
||||||
@@ -138,16 +171,16 @@ constexpr
|
|||||||
sliding != prevSliding ? constexpr_pext(sliding, attacks) : m.attacks[size - 1];
|
sliding != prevSliding ? constexpr_pext(sliding, attacks) : m.attacks[size - 1];
|
||||||
prevSliding = sliding;
|
prevSliding = sliding;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
occupancy[size] = b;
|
occupancy[size] = b;
|
||||||
reference[size] = Bitboards::sliding_attack(pt, s, b);
|
reference[size] = Bitboards::sliding_attack(pt, s, b);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
size++;
|
size++;
|
||||||
b = (b - m.mask) & m.mask;
|
b = (b - m.mask) & m.mask;
|
||||||
} while (b);
|
} while (b);
|
||||||
|
|
||||||
#ifndef USE_PEXT
|
#ifndef USE_PEXT
|
||||||
PRNG rng(seeds[Is64Bit][rank_of(s)]);
|
PRNG rng(seeds[Is64Bit][rank_of(s)]);
|
||||||
|
|
||||||
// Find a magic for square 's' picking up an (almost) random number
|
// Find a magic for square 's' picking up an (almost) random number
|
||||||
@@ -176,11 +209,11 @@ constexpr
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_COMPTIME_ATTACKS) && defined(USE_PEXT)
|
#if defined(USE_COMPTIME_ATTACKS) && defined(USE_PEXT)
|
||||||
constexpr auto RookTable = []() {
|
constexpr auto RookTable = []() {
|
||||||
std::array<uint16_t, 0x19000> result{};
|
std::array<uint16_t, 0x19000> result{};
|
||||||
Magic magics[64][2] = {};
|
Magic magics[64][2] = {};
|
||||||
@@ -193,12 +226,13 @@ constexpr auto BishopTable = []() {
|
|||||||
init_magics(BISHOP, result.data(), magics, false);
|
init_magics(BISHOP, result.data(), magics, false);
|
||||||
return result;
|
return result;
|
||||||
}();
|
}();
|
||||||
#else
|
#else
|
||||||
std::array<MagicMask, 0x19000> RookTable;
|
std::array<MagicMask, 0x19000> RookTable;
|
||||||
std::array<MagicMask, 0x1480> BishopTable;
|
std::array<MagicMask, 0x1480> BishopTable;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// Initializes various bitboard tables. It is called at
|
// Initializes various bitboard tables. It is called at
|
||||||
// startup and relies on global objects to be already zero-initialized.
|
// startup and relies on global objects to be already zero-initialized.
|
||||||
@@ -211,8 +245,12 @@ void Bitboards::init() {
|
|||||||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
||||||
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
|
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
|
||||||
|
|
||||||
|
#ifdef USE_HYPERBOLA_QUINT
|
||||||
|
init_magics(Magics);
|
||||||
|
#else
|
||||||
init_magics(ROOK, const_cast<MagicMask*>(RookTable.data()), Magics, true);
|
init_magics(ROOK, const_cast<MagicMask*>(RookTable.data()), Magics, true);
|
||||||
init_magics(BISHOP, const_cast<MagicMask*>(BishopTable.data()), Magics, true);
|
init_magics(BISHOP, const_cast<MagicMask*>(BishopTable.data()), Magics, true);
|
||||||
|
#endif
|
||||||
|
|
||||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||||
{
|
{
|
||||||
|
|||||||
+37
-9
@@ -31,6 +31,11 @@
|
|||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
#ifdef __aarch64__
|
||||||
|
#include <arm_acle.h>
|
||||||
|
#define USE_HYPERBOLA_QUINT
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace Bitboards {
|
namespace Bitboards {
|
||||||
@@ -74,41 +79,64 @@ extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
|||||||
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||||
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
|
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
|
||||||
|
|
||||||
|
#ifdef USE_HYPERBOLA_QUINT
|
||||||
|
// Hyperbola quintessence implementation for ARM, thanks to the availability of an
|
||||||
|
// efficient bit reversal instruction.
|
||||||
|
// See https://www.chessprogramming.org/Hyperbola_Quintessence
|
||||||
|
struct Magic {
|
||||||
|
// For rooks: file attacks, rank attacks. For bishops: diagonal/antidiagonal
|
||||||
|
Bitboard mask1, mask2;
|
||||||
|
// Precomputed 2 * square_bb(sq), 2 * reverse(square_bb(sq))
|
||||||
|
Bitboard r, rr;
|
||||||
|
|
||||||
|
Bitboard hyperbola(Bitboard occupied, Bitboard mask) const {
|
||||||
|
Bitboard o = occupied & mask;
|
||||||
|
Bitboard fwd = o - r;
|
||||||
|
Bitboard rev = __rbitll(o) - rr;
|
||||||
|
return (fwd ^ __rbitll(rev)) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitboard attacks_bb(Bitboard occupied) const {
|
||||||
|
return hyperbola(occupied, mask1) | hyperbola(occupied, mask2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#else
|
||||||
// Magic holds all magic bitboards relevant data for a single square
|
// Magic holds all magic bitboards relevant data for a single square
|
||||||
struct Magic {
|
struct Magic {
|
||||||
Bitboard mask;
|
Bitboard mask;
|
||||||
#ifdef USE_PEXT
|
#ifdef USE_PEXT
|
||||||
uint16_t* attacks;
|
uint16_t* attacks;
|
||||||
Bitboard pseudoAttacks;
|
Bitboard pseudoAttacks;
|
||||||
#else
|
#else
|
||||||
Bitboard* attacks;
|
Bitboard* attacks;
|
||||||
Bitboard magic;
|
Bitboard magic;
|
||||||
unsigned shift;
|
unsigned shift;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Compute the attack's index using the 'magic bitboards' approach
|
// Compute the attack's index using the 'magic bitboards' approach
|
||||||
unsigned index(Bitboard occupied) const {
|
unsigned index(Bitboard occupied) const {
|
||||||
|
|
||||||
#ifdef USE_PEXT
|
#ifdef USE_PEXT
|
||||||
return unsigned(pext(occupied, mask));
|
return unsigned(pext(occupied, mask));
|
||||||
#else
|
#else
|
||||||
if (Is64Bit)
|
if (Is64Bit)
|
||||||
return unsigned(((occupied & mask) * magic) >> shift);
|
return unsigned(((occupied & mask) * magic) >> shift);
|
||||||
|
|
||||||
unsigned lo = unsigned(occupied) & unsigned(mask);
|
unsigned lo = unsigned(occupied) & unsigned(mask);
|
||||||
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
|
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
|
||||||
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
|
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitboard attacks_bb(Bitboard occupied) const {
|
Bitboard attacks_bb(Bitboard occupied) const {
|
||||||
#ifdef USE_PEXT
|
#ifdef USE_PEXT
|
||||||
return pdep(attacks[index(occupied)], pseudoAttacks);
|
return pdep(attacks[index(occupied)], pseudoAttacks);
|
||||||
#else
|
#else
|
||||||
return attacks[index(occupied)];
|
return attacks[index(occupied)];
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
extern Magic Magics[SQUARE_NB][2];
|
extern Magic Magics[SQUARE_NB][2];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user