Skip to content

Commit 1588642

Browse files
Ernesto Gattiglinscott
authored andcommitted
Simpler PRNG and faster magics search
This patch replaces RKISS by a simpler and faster PRNG, xorshift64* proposed by S. Vigna (2014). It is extremely simple, has a large enough period for Stockfish's needs (2^64), requires no warming-up (allowing such code to be removed), and offers slightly better randomness than MT19937. Paper: http://xorshift.di.unimi.it/ Reference source code (public domain): http://xorshift.di.unimi.it/xorshift64star.c The patch also simplifies how init_magics() searches for magics: - Old logic: seed the PRNG always with the same seed, then use optimized bit rotations to tailor the RNG sequence per rank. - New logic: seed the PRNG with an optimized seed per rank. This has two advantages: 1. Less code and less computation to perform during magics search (not ROTL). 2. More choices for random sequence tuning. The old logic only let us choose from 4096 bit rotation pairs. With the new one, we can look for the best seeds among 2^64 values. Indeed, the set of seeds[][] provided in the patch reduces the effort needed to find the magics: 64-bit SF: Old logic -> 5,783,789 rand64() calls needed to find the magics New logic -> 4,420,086 calls 32-bit SF: Old logic -> 2,175,518 calls New logic -> 1,895,955 calls In the 64-bit case, init_magics() take 25 ms less to complete (Intel Core i5). Finally, when playing with strength handicap, non-determinism is achieved by setting the seed of the static RNG only once. Afterwards, there is no need to skip output values. The bench only changes because the Zobrist keys are now different (since they are random numbers straight out of the PRNG). The RNG seed has been carefully chosen so that the resulting Zobrist keys are particularly well-behaved: 1. All triplets of XORed keys are unique, implying that it would take at least 7 keys to find a 64-bit collision (test suggested by ceebo) 2. All pairs of XORed keys are unique modulo 2^32 3. The cardinality of { (key1 ^ key2) >> 48 } is as close as possible to the maximum (65536) Point 2 aims at ensuring a good distribution among the bits that determine an TT entry's cluster, likewise point 3 among the bits that form the TT entry's key16 inside a cluster. Details: Bitset card(key1^key2) ------ --------------- RKISS key16 64894 = 99.020% of theoretical maximum low18 180117 = 99.293% low32 305362 = 99.997% Xorshift64*, old seed key16 64918 = 99.057% low18 179994 = 99.225% low32 305350 = 99.993% Xorshift64*, new seed key16 65027 = 99.223% low18 181118 = 99.845% low32 305371 = 100.000% Bench: 9324905 Resolves #148
1 parent a87da2c commit 1588642

File tree

5 files changed

+50
-102
lines changed

5 files changed

+50
-102
lines changed

src/bitboard.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
#include "bitboard.h"
2424
#include "bitcount.h"
25-
#include "rkiss.h"
25+
#include "misc.h"
2626

2727
Bitboard RookMasks[SQUARE_NB];
2828
Bitboard RookMagics[SQUARE_NB];
@@ -250,11 +250,10 @@ namespace {
250250
void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[],
251251
Bitboard masks[], unsigned shifts[], Square deltas[], Fn index) {
252252

253-
int MagicBoosters[][RANK_NB] = { { 969, 1976, 2850, 542, 2069, 2852, 1708, 164 },
254-
{ 3101, 552, 3555, 926, 834, 26, 2131, 1117 } };
255-
RKISS rk;
253+
int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 },
254+
{ 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } };
256255
Bitboard occupancy[4096], reference[4096], edges, b;
257-
int i, size, booster;
256+
int i, size;
258257

259258
// attacks[s] is a pointer to the beginning of the attacks table for square 's'
260259
attacks[SQ_A1] = table;
@@ -294,13 +293,13 @@ namespace {
294293
if (HasPext)
295294
continue;
296295

297-
booster = MagicBoosters[Is64Bit][rank_of(s)];
296+
PRNG rng(seeds[Is64Bit][rank_of(s)]);
298297

299298
// Find a magic for square 's' picking up an (almost) random number
300299
// until we find the one that passes the verification test.
301300
do {
302301
do
303-
magics[s] = rk.magic_rand<Bitboard>(booster);
302+
magics[s] = rng.sparse_rand<Bitboard>();
304303
while (popcount<Max15>((magics[s] * masks[s]) >> 56) < 6);
305304

306305
std::memset(attacks[s], 0, size * sizeof(Bitboard));

src/misc.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,37 @@ std::ostream& operator<<(std::ostream&, SyncCout);
5959
#define sync_cout std::cout << IO_LOCK
6060
#define sync_endl std::endl << IO_UNLOCK
6161

62+
63+
/// xorshift64star Pseudo-Random Number Generator
64+
/// This class is based on original code written and dedicated
65+
/// to the public domain by Sebastiano Vigna (2014).
66+
/// It has the following characteristics:
67+
/// - Outputs 64-bit numbers
68+
/// - Passes Dieharder and SmallCrush test batteries
69+
/// - Does not require warm-up, no zeroland to escape
70+
/// - Internal state is a single 64-bit integer
71+
/// - Period is 2^64 - 1
72+
/// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
73+
/// For further analysis see
74+
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
75+
76+
class PRNG {
77+
78+
uint64_t x;
79+
80+
uint64_t rand64() {
81+
x^=x>>12; x^=x<<25; x^=x>>27;
82+
return x * 2685821657736338717LL;
83+
}
84+
85+
public:
86+
PRNG(uint64_t seed) : x(seed) { assert(seed); }
87+
88+
template<typename T> T rand() { return T(rand64()); }
89+
90+
/// Special generator used to fast init magic numbers.
91+
/// Output values only have 1/8th of their bits set on average.
92+
template<typename T> T sparse_rand() { return T(rand64() & rand64() & rand64()); }
93+
};
94+
6295
#endif // #ifndef MISC_H_INCLUDED

src/position.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#include "movegen.h"
2828
#include "position.h"
2929
#include "psqtab.h"
30-
#include "rkiss.h"
30+
#include "misc.h"
3131
#include "thread.h"
3232
#include "tt.h"
3333
#include "uci.h"
@@ -137,28 +137,28 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
137137

138138
void Position::init() {
139139

140-
RKISS rk;
140+
PRNG rng(1070372);
141141

142142
for (Color c = WHITE; c <= BLACK; ++c)
143143
for (PieceType pt = PAWN; pt <= KING; ++pt)
144144
for (Square s = SQ_A1; s <= SQ_H8; ++s)
145-
Zobrist::psq[c][pt][s] = rk.rand<Key>();
145+
Zobrist::psq[c][pt][s] = rng.rand<Key>();
146146

147147
for (File f = FILE_A; f <= FILE_H; ++f)
148-
Zobrist::enpassant[f] = rk.rand<Key>();
148+
Zobrist::enpassant[f] = rng.rand<Key>();
149149

150150
for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
151151
{
152152
Bitboard b = cr;
153153
while (b)
154154
{
155155
Key k = Zobrist::castling[1ULL << pop_lsb(&b)];
156-
Zobrist::castling[cr] ^= k ? k : rk.rand<Key>();
156+
Zobrist::castling[cr] ^= k ? k : rng.rand<Key>();
157157
}
158158
}
159159

160-
Zobrist::side = rk.rand<Key>();
161-
Zobrist::exclusion = rk.rand<Key>();
160+
Zobrist::side = rng.rand<Key>();
161+
Zobrist::exclusion = rng.rand<Key>();
162162

163163
for (PieceType pt = PAWN; pt <= KING; ++pt)
164164
{

src/rkiss.h

Lines changed: 0 additions & 81 deletions
This file was deleted.

src/search.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#include "evaluate.h"
2828
#include "movegen.h"
2929
#include "movepick.h"
30-
#include "rkiss.h"
30+
#include "misc.h"
3131
#include "search.h"
3232
#include "timeman.h"
3333
#include "thread.h"
@@ -1377,11 +1377,8 @@ namespace {
13771377

13781378
Move Skill::pick_move() {
13791379

1380-
static RKISS rk;
1381-
1382-
// PRNG sequence should be not deterministic
1383-
for (int i = Time::now() % 50; i > 0; --i)
1384-
rk.rand<unsigned>();
1380+
// PRNG sequence should be non-deterministic, so we seed it with the time at init
1381+
static PRNG rng(Time::now());
13851382

13861383
// RootMoves are already sorted by score in descending order
13871384
int variance = std::min(RootMoves[0].score - RootMoves[candidates - 1].score, PawnValueMg);
@@ -1402,7 +1399,7 @@ namespace {
14021399

14031400
// This is our magic formula
14041401
score += ( weakness * int(RootMoves[0].score - score)
1405-
+ variance * (rk.rand<unsigned>() % weakness)) / 128;
1402+
+ variance * (rng.rand<unsigned>() % weakness)) / 128;
14061403

14071404
if (score > maxScore)
14081405
{

0 commit comments

Comments
 (0)