Skip to content

Commit

Permalink
Use dense precomputed fixed shift magics
Browse files Browse the repository at this point in the history
These densely packed fixed shift magics computed by Volker Annuss
achieve a considerably smaller table size than the current magics with
variable shift.

PEXT-Bitboards do not benefit (and replacing them would be a regression
on bmi2).

Speedup on x86-64-modern:

base =    1848534 +/- 17362
test =    1863050 +/- 16807
diff =      14515 +/- 2490
speedup = 0.007853

No regression on x86-64-bmi2:

base =    1892739 +/- 18354
test =    1892892 +/- 18460
diff =        153 +/- 3092
speedup = 0.000081

STC with disabled bmi2:

LLR: 2.95 (-2.94,2.94) [0.00,5.00]
Total: 46087 W: 8656 L: 8331 D: 29100

No functional change.
  • Loading branch information
niklasf committed Jan 13, 2018
1 parent 7d4d3a2 commit fc04238
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 78 deletions.
250 changes: 179 additions & 71 deletions src/bitboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,150 @@ namespace {

int MSBTable[256]; // To implement software msb()
Square BSFTable[SQUARE_NB]; // To implement software bitscan
Bitboard RookTable[0x19000]; // To store rook attacks
Bitboard BishopTable[0x1480]; // To store bishop attacks

void init_magics(Bitboard table[], Magic magics[], Direction directions[]);
Bitboard AttackTable[HasPext ? 107648 : 88772] = { 0 };

struct MagicInit {
Bitboard magic;
unsigned offset;
};

MagicInit BishopMagicInit[SQUARE_NB] = {
{ 0x007fbfbfbfbfbfffu, 5378 },
{ 0x0000a060401007fcu, 4093 },
{ 0x0001004008020000u, 4314 },
{ 0x0000806004000000u, 6587 },
{ 0x0000100400000000u, 6491 },
{ 0x000021c100b20000u, 6330 },
{ 0x0000040041008000u, 5609 },
{ 0x00000fb0203fff80u, 22236 },
{ 0x0000040100401004u, 6106 },
{ 0x0000020080200802u, 5625 },
{ 0x0000004010202000u, 16785 },
{ 0x0000008060040000u, 16817 },
{ 0x0000004402000000u, 6842 },
{ 0x0000000801008000u, 7003 },
{ 0x000007efe0bfff80u, 4197 },
{ 0x0000000820820020u, 7356 },
{ 0x0000400080808080u, 4602 },
{ 0x00021f0100400808u, 4538 },
{ 0x00018000c06f3fffu, 29531 },
{ 0x0000258200801000u, 45393 },
{ 0x0000240080840000u, 12420 },
{ 0x000018000c03fff8u, 15763 },
{ 0x00000a5840208020u, 5050 },
{ 0x0000020008208020u, 4346 },
{ 0x0000804000810100u, 6074 },
{ 0x0001011900802008u, 7866 },
{ 0x0000804000810100u, 32139 },
{ 0x000100403c0403ffu, 57673 },
{ 0x00078402a8802000u, 55365 },
{ 0x0000101000804400u, 15818 },
{ 0x0000080800104100u, 5562 },
{ 0x00004004c0082008u, 6390 },
{ 0x0001010120008020u, 7930 },
{ 0x000080809a004010u, 13329 },
{ 0x0007fefe08810010u, 7170 },
{ 0x0003ff0f833fc080u, 27267 },
{ 0x007fe08019003042u, 53787 },
{ 0x003fffefea003000u, 5097 },
{ 0x0000101010002080u, 6643 },
{ 0x0000802005080804u, 6138 },
{ 0x0000808080a80040u, 7418 },
{ 0x0000104100200040u, 7898 },
{ 0x0003ffdf7f833fc0u, 42012 },
{ 0x0000008840450020u, 57350 },
{ 0x00007ffc80180030u, 22813 },
{ 0x007fffdd80140028u, 56693 },
{ 0x00020080200a0004u, 5818 },
{ 0x0000101010100020u, 7098 },
{ 0x0007ffdfc1805000u, 4451 },
{ 0x0003ffefe0c02200u, 4709 },
{ 0x0000000820806000u, 4794 },
{ 0x0000000008403000u, 13364 },
{ 0x0000000100202000u, 4570 },
{ 0x0000004040802000u, 4282 },
{ 0x0004010040100400u, 14964 },
{ 0x00006020601803f4u, 4026 },
{ 0x0003ffdfdfc28048u, 4826 },
{ 0x0000000820820020u, 7354 },
{ 0x0000000008208060u, 4848 },
{ 0x0000000000808020u, 15946 },
{ 0x0000000001002020u, 14932 },
{ 0x0000000401002008u, 16588 },
{ 0x0000004040404040u, 6905 },
{ 0x007fff9fdf7ff813u, 16076 }
};

MagicInit RookMagicInit[SQUARE_NB] = {
{ 0x00280077ffebfffeu, 26304 },
{ 0x2004010201097fffu, 35520 },
{ 0x0010020010053fffu, 38592 },
{ 0x0040040008004002u, 8026 },
{ 0x7fd00441ffffd003u, 22196 },
{ 0x4020008887dffffeu, 80870 },
{ 0x004000888847ffffu, 76747 },
{ 0x006800fbff75fffdu, 30400 },
{ 0x000028010113ffffu, 11115 },
{ 0x0020040201fcffffu, 18205 },
{ 0x007fe80042ffffe8u, 53577 },
{ 0x00001800217fffe8u, 62724 },
{ 0x00001800073fffe8u, 34282 },
{ 0x00001800e05fffe8u, 29196 },
{ 0x00001800602fffe8u, 23806 },
{ 0x000030002fffffa0u, 49481 },
{ 0x00300018010bffffu, 2410 },
{ 0x0003000c0085fffbu, 36498 },
{ 0x0004000802010008u, 24478 },
{ 0x0004002020020004u, 10074 },
{ 0x0001002002002001u, 79315 },
{ 0x0001001000801040u, 51779 },
{ 0x0000004040008001u, 13586 },
{ 0x0000006800cdfff4u, 19323 },
{ 0x0040200010080010u, 70612 },
{ 0x0000080010040010u, 83652 },
{ 0x0004010008020008u, 63110 },
{ 0x0000040020200200u, 34496 },
{ 0x0002008010100100u, 84966 },
{ 0x0000008020010020u, 54341 },
{ 0x0000008020200040u, 60421 },
{ 0x0000820020004020u, 86402 },
{ 0x00fffd1800300030u, 50245 },
{ 0x007fff7fbfd40020u, 76622 },
{ 0x003fffbd00180018u, 84676 },
{ 0x001fffde80180018u, 78757 },
{ 0x000fffe0bfe80018u, 37346 },
{ 0x0001000080202001u, 370 },
{ 0x0003fffbff980180u, 42182 },
{ 0x0001fffdff9000e0u, 45385 },
{ 0x00fffefeebffd800u, 61659 },
{ 0x007ffff7ffc01400u, 12790 },
{ 0x003fffbfe4ffe800u, 16762 },
{ 0x001ffff01fc03000u, 0 },
{ 0x000fffe7f8bfe800u, 38380 },
{ 0x0007ffdfdf3ff808u, 11098 },
{ 0x0003fff85fffa804u, 21803 },
{ 0x0001fffd75ffa802u, 39189 },
{ 0x00ffffd7ffebffd8u, 58628 },
{ 0x007fff75ff7fbfd8u, 44116 },
{ 0x003fff863fbf7fd8u, 78357 },
{ 0x001fffbfdfd7ffd8u, 44481 },
{ 0x000ffff810280028u, 64134 },
{ 0x0007ffd7f7feffd8u, 41759 },
{ 0x0003fffc0c480048u, 1394 },
{ 0x0001ffffafd7ffd8u, 40910 },
{ 0x00ffffe4ffdfa3bau, 66516 },
{ 0x007fffef7ff3d3dau, 3897 },
{ 0x003fffbfdfeff7fau, 3930 },
{ 0x001fffeff7fbfc22u, 72934 },
{ 0x0000020408001001u, 72662 },
{ 0x0007fffeffff77fdu, 56325 },
{ 0x0003ffffbf7dfeecu, 66501 },
{ 0x0001ffff9dffa333u, 14826 }
};

Bitboard relevant_occupancies(Direction directions[], Square s);
void init_magics(MagicInit init[], Magic magics[], Direction directions[], unsigned shift);

// bsf_index() returns the index into BSFTable[] to look up the bitscan. Uses
// Matt Taylor's folding for 32 bit case, extended to 64 bit by Kim Walisch.
Expand Down Expand Up @@ -202,8 +342,25 @@ void Bitboards::init() {
Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST };
Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST };

init_magics(RookTable, RookMagics, RookDirections);
init_magics(BishopTable, BishopMagics, BishopDirections);
if (HasPext)
{
unsigned offset = 0;

for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
RookMagicInit[s].offset = offset;
offset += 1 << popcount(relevant_occupancies(RookDirections, s));
}

for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
BishopMagicInit[s].offset = offset;
offset += 1 << popcount(relevant_occupancies(BishopDirections, s));
}
}

init_magics(RookMagicInit, RookMagics, RookDirections, 12);
init_magics(BishopMagicInit, BishopMagics, BishopDirections, 9);

for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{
Expand Down Expand Up @@ -243,84 +400,35 @@ namespace {
return attack;
}

Bitboard relevant_occupancies(Direction directions[], Square s) {

// init_magics() computes all rook and bishop attacks at startup. Magic
// bitboards are used to look up attacks of sliding pieces. As a reference see
// chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we
// use the so called "fancy" approach.

void init_magics(Bitboard table[], Magic magics[], Direction directions[]) {
Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));
return sliding_attack(directions, s, 0) & ~edges;
}

// 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 } };
// Magic bitboards are used to look up attacks of sliding pieces.
// init_magics() initializes the attack tables from precomputed fixed shift
// magics with overlapping index ranges:
// <https://chessprogramming.wikispaces.com/Magic+Bitboards#FixedShiftFancy>

Bitboard occupancy[4096], reference[4096], edges, b;
int epoch[4096] = {}, cnt = 0, size = 0;
void init_magics(MagicInit init[], Magic magics[], Direction directions[], unsigned shift) {

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));

// 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];
m.mask = sliding_attack(directions, s, 0) & ~edges;
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);

// 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.magic = init[s].magic;
m.mask = relevant_occupancies(directions, s);
m.attacks = AttackTable + init[s].offset;

// 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 {
occupancy[size] = b;
reference[size] = sliding_attack(directions, s, b);

if (HasPext)
m.attacks[pext(b, m.mask)] = reference[size];

size++;
unsigned idx = HasPext ? pext(b, m.mask) : (m.magic * b) >> (64 - shift);
Bitboard attack = sliding_attack(directions, s, b);
assert(!m.attacks[idx] || m.attacks[idx] == attack);
m.attacks[idx] = attack;
b = (b - m.mask) & m.mask;
} while (b);

if (HasPext)
continue;

PRNG rng(seeds[Is64Bit][rank_of(s)]);

// Find a magic for square 's' picking up an (almost) random number
// until we find the one that passes the verification test.
for (int i = 0; i < size; )
{
for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; )
m.magic = rng.sparse_rand<Bitboard>();

// A good magic must map every possible occupancy to an index that
// looks up the correct sliding attack in the attacks[s] database.
// Note that we build up the database for square 's' as a side
// effect of verifying the magic. Keep track of the attempt count
// and save it in epoch[], little speed-up trick to avoid resetting
// m.attacks[] after every failed attempt.
for (++cnt, i = 0; i < size; ++i)
{
unsigned idx = m.index(occupancy[i]);

if (epoch[idx] < cnt)
{
epoch[idx] = cnt;
m.attacks[idx] = reference[i];
}
else if (m.attacks[idx] != reference[i])
break;
}
}
}
}
}
11 changes: 4 additions & 7 deletions src/bitboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,17 @@ struct Magic {
Bitboard mask;
Bitboard magic;
Bitboard* attacks;
unsigned shift;

// Compute the attack's index using the 'magic bitboards' approach
template<PieceType Pt>
unsigned index(Bitboard occupied) const {

if (HasPext)
return unsigned(pext(occupied, mask));

if (Is64Bit)
return unsigned(((occupied & mask) * magic) >> shift);
unsigned shift = 64 - (Pt == ROOK ? 12 : 9);

unsigned lo = unsigned(occupied) & unsigned(mask);
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
return unsigned(((occupied & mask) * magic) >> shift);
}
};

Expand Down Expand Up @@ -242,7 +239,7 @@ template<PieceType Pt>
inline Bitboard attacks_bb(Square s, Bitboard occupied) {

const Magic& m = Pt == ROOK ? RookMagics[s] : BishopMagics[s];
return m.attacks[m.index(occupied)];
return m.attacks[m.index<Pt>(occupied)];
}

inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
Expand Down

0 comments on commit fc04238

Please sign in to comment.