Skip to content

Commit

Permalink
Simpler PRNG and faster magics search
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Ernesto Gatti authored and glinscott committed Dec 8, 2014
1 parent a87da2c commit 1588642
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 102 deletions.
13 changes: 6 additions & 7 deletions src/bitboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

#include "bitboard.h"
#include "bitcount.h"
#include "rkiss.h"
#include "misc.h"

Bitboard RookMasks[SQUARE_NB];
Bitboard RookMagics[SQUARE_NB];
Expand Down Expand Up @@ -250,11 +250,10 @@ namespace {
void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[],
Bitboard masks[], unsigned shifts[], Square deltas[], Fn index) {

int MagicBoosters[][RANK_NB] = { { 969, 1976, 2850, 542, 2069, 2852, 1708, 164 },
{ 3101, 552, 3555, 926, 834, 26, 2131, 1117 } };
RKISS rk;
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 i, size, booster;
int i, size;

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

booster = MagicBoosters[Is64Bit][rank_of(s)];
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.
do {
do
magics[s] = rk.magic_rand<Bitboard>(booster);
magics[s] = rng.sparse_rand<Bitboard>();
while (popcount<Max15>((magics[s] * masks[s]) >> 56) < 6);

std::memset(attacks[s], 0, size * sizeof(Bitboard));
Expand Down
33 changes: 33 additions & 0 deletions src/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,37 @@ std::ostream& operator<<(std::ostream&, SyncCout);
#define sync_cout std::cout << IO_LOCK
#define sync_endl std::endl << IO_UNLOCK


/// xorshift64star Pseudo-Random Number Generator
/// This class is based on original code written and dedicated
/// to the public domain by Sebastiano Vigna (2014).
/// It has the following characteristics:
/// - Outputs 64-bit numbers
/// - Passes Dieharder and SmallCrush test batteries
/// - Does not require warm-up, no zeroland to escape
/// - Internal state is a single 64-bit integer
/// - Period is 2^64 - 1
/// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
/// For further analysis see
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>

class PRNG {

uint64_t x;

uint64_t rand64() {
x^=x>>12; x^=x<<25; x^=x>>27;
return x * 2685821657736338717LL;
}

public:
PRNG(uint64_t seed) : x(seed) { assert(seed); }

template<typename T> T rand() { return T(rand64()); }

/// Special generator used to fast init magic numbers.
/// Output values only have 1/8th of their bits set on average.
template<typename T> T sparse_rand() { return T(rand64() & rand64() & rand64()); }
};

#endif // #ifndef MISC_H_INCLUDED
14 changes: 7 additions & 7 deletions src/position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
#include "movegen.h"
#include "position.h"
#include "psqtab.h"
#include "rkiss.h"
#include "misc.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
Expand Down Expand Up @@ -137,28 +137,28 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {

void Position::init() {

RKISS rk;
PRNG rng(1070372);

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

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

for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
{
Bitboard b = cr;
while (b)
{
Key k = Zobrist::castling[1ULL << pop_lsb(&b)];
Zobrist::castling[cr] ^= k ? k : rk.rand<Key>();
Zobrist::castling[cr] ^= k ? k : rng.rand<Key>();
}
}

Zobrist::side = rk.rand<Key>();
Zobrist::exclusion = rk.rand<Key>();
Zobrist::side = rng.rand<Key>();
Zobrist::exclusion = rng.rand<Key>();

for (PieceType pt = PAWN; pt <= KING; ++pt)
{
Expand Down
81 changes: 0 additions & 81 deletions src/rkiss.h

This file was deleted.

11 changes: 4 additions & 7 deletions src/search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
#include "evaluate.h"
#include "movegen.h"
#include "movepick.h"
#include "rkiss.h"
#include "misc.h"
#include "search.h"
#include "timeman.h"
#include "thread.h"
Expand Down Expand Up @@ -1377,11 +1377,8 @@ namespace {

Move Skill::pick_move() {

static RKISS rk;

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

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

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

if (score > maxScore)
{
Expand Down

0 comments on commit 1588642

Please sign in to comment.