HQ attacks for AVX2

passed STC (https://tests.stockfishchess.org/tests/view/6a1157dc818cacc1db0ac172):

LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 29792 W: 7759 L: 7465 D: 14568
Ptnml(0-2): 75, 3206, 8033, 3514, 68

Also passed STC after cleanups https://tests.stockfishchess.org/tests/view/6a121beb818cacc1db0ac35e

vondele's local test:

Result of 100 runs
base (./stockfish.master       ) =    1136025  +/- 2816
test (./stockfish.patch2       ) =    1171370  +/- 2848
diff                             =     +35346  +/- 3275

speedup        = +0.0311
P(speedup > 0) =  1.0000

Basically we just do hyperbola quintessence in parallel. AVX2 doesn't have efficient bit reversal so we only do it for file and bishop attacks, then do rank attacks separately with a lookup table. This LUT is much smaller which is why this seems to be faster than standard magics.

closes https://github.com/official-stockfish/Stockfish/pull/6845

No functional change
This commit is contained in:
anematode
2026-05-25 11:20:24 +02:00
committed by Joost VandeVondele
parent 41f3557db9
commit 77a8f6ccf3
3 changed files with 143 additions and 7 deletions
+55 -3
View File
@@ -30,7 +30,11 @@ Bitboard LineBB[SQUARE_NB][SQUARE_NB];
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
#ifdef USE_DUAL_HYPERBOLA_QUINT
alignas(64) DualMagic DualMagics[SQUARE_NB];
#else
alignas(64) Magic Magics[SQUARE_NB][2];
#endif
}
@@ -40,8 +44,7 @@ using MagicMask = uint16_t;
using MagicMask = Bitboard;
#endif
#ifdef USE_HYPERBOLA_QUINT
static Bitboard line_mask(Square sq, Direction d1, Direction d2) {
[[maybe_unused]] static Bitboard line_mask(Square sq, Direction d1, Direction d2) {
Bitboard mask = 0, dest;
for (Direction d : {d1, d2})
{
@@ -55,6 +58,7 @@ static Bitboard line_mask(Square sq, Direction d1, Direction d2) {
return mask;
}
#ifdef USE_HYPERBOLA_QUINT
static void init_magics(Magic magics[][2]) {
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
@@ -71,6 +75,48 @@ static void init_magics(Magic magics[][2]) {
}
}
#elif defined(USE_DUAL_HYPERBOLA_QUINT)
// Sliding attacks within a rank, indexed by the slider's file and the
// 8-bit rank occupancy, yielding the 8-bit attack set on that rank
constexpr auto RankAttacks = []() {
std::array<std::array<uint8_t, 256>, FILE_NB> table{};
for (int file = 0; file < 8; ++file)
for (int occ = 0; occ < 256; ++occ)
{
uint8_t attacks = 0;
for (int f = file + 1; f <= 7; ++f)
{
attacks |= uint8_t(1 << f);
if (occ & (1 << f))
break;
}
for (int f = file - 1; f >= 0; --f)
{
attacks |= uint8_t(1 << f);
if (occ & (1 << f))
break;
}
table[file][occ] = attacks;
}
return table;
}();
static void init_dual_magics(DualMagic magics[]) {
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
DualMagic& m = magics[s];
m.maskFile = line_mask(s, NORTH, SOUTH);
m.maskDiag = line_mask(s, NORTH_EAST, SOUTH_WEST);
m.maskNone = 0;
m.maskAntidiag = line_mask(s, NORTH_WEST, SOUTH_EAST);
m.r = square_bb(s) * 2;
m.rr = square_bb(Square(63 - int(s))) * 2;
m.rankAttacksLookup = RankAttacks[int(file_of(s))].data();
m.shift = 8 * int(rank_of(s));
}
}
#else
namespace {
@@ -181,7 +227,7 @@ constexpr auto BishopTable = []() {
init_magics(BISHOP, result.data(), magics, false);
return result;
}();
#else
#elif !defined(USE_DUAL_HYPERBOLA_QUINT) && !defined(USE_HYPERBOLA_QUINT)
std::array<MagicMask, 0x19000> RookTable;
std::array<MagicMask, 0x1480> BishopTable;
#endif
@@ -193,6 +239,8 @@ void init() {
#ifdef USE_HYPERBOLA_QUINT
init_magics(Magics);
#elif defined(USE_DUAL_HYPERBOLA_QUINT)
init_dual_magics(DualMagics);
#else
init_magics(ROOK, const_cast<MagicMask*>(RookTable.data()), Magics, true);
init_magics(BISHOP, const_cast<MagicMask*>(BishopTable.data()), Magics, true);
@@ -216,10 +264,14 @@ void init() {
}
}
#ifdef USE_DUAL_HYPERBOLA_QUINT
const DualMagic& dual_magic(Square s) { return DualMagics[s]; }
#else
const Magic& magic(Square s, PieceType pt) {
assert((pt == BISHOP || pt == ROOK) && is_ok(s));
return Magics[s][pt - BISHOP];
}
#endif
Bitboard line_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2));