diff --git a/src/Makefile b/src/Makefile index cb55c8b..00eed66 100644 --- a/src/Makefile +++ b/src/Makefile @@ -35,7 +35,7 @@ BINDIR = $(PREFIX)/bin PGOBENCH = ./$(EXE) bench 16 1 1000 default time ### Object files -OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ +OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o key.o main.o \ material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \ search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o @@ -413,7 +413,7 @@ profile-build: $(PGOBENCH) > /dev/null @echo "" @echo "Step 3/4. Building final executable ..." - @touch *.cpp *.h syzygy/*.cpp syzygy/*.h + touch *.cpp *.h syzygy/*.cpp syzygy/*.h $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use) @echo "" @echo "Step 4/4. Deleting profile data ..." @@ -458,16 +458,16 @@ config-sanity: @echo "" @echo "Testing config sanity. If this fails, try 'make help' ..." @echo "" - @test "$(debug)" = "yes" || test "$(debug)" = "no" - @test "$(optimize)" = "yes" || test "$(optimize)" = "no" - @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ + test "$(debug)" = "yes" || test "$(debug)" = "no" + test "$(optimize)" = "yes" || test "$(optimize)" = "no" + test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7" - @test "$(bits)" = "32" || test "$(bits)" = "64" - @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" - @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" - @test "$(sse)" = "yes" || test "$(sse)" = "no" - @test "$(pext)" = "yes" || test "$(pext)" = "no" - @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" + test "$(bits)" = "32" || test "$(bits)" = "64" + test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" + test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" + test "$(sse)" = "yes" || test "$(sse)" = "no" + test "$(pext)" = "yes" || test "$(pext)" = "no" + test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" $(EXE): $(OBJS) $(CXX) -o $@ $(OBJS) $(LDFLAGS) @@ -488,11 +488,11 @@ gcc-profile-use: all gcc-profile-clean: - @rm -rf *.gcda *.gcno syzygy/*.gcda syzygy/*.gcno bench.txt + rm -rf *.gcda *.gcno syzygy/*.gcda syzygy/*.gcno bench.txt icc-profile-prepare: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) icc-profile-clean - @mkdir profdir + mkdir profdir icc-profile-make: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ @@ -505,10 +505,10 @@ icc-profile-use: all icc-profile-clean: - @rm -rf profdir bench.txt + rm -rf profdir bench.txt .depend: - -@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null + -$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null -include .depend diff --git a/src/key.cpp b/src/key.cpp new file mode 100644 index 0000000..45cf3ea --- /dev/null +++ b/src/key.cpp @@ -0,0 +1,14 @@ +#include "types.h" +#include "misc.h" +#include + +Key Key::rand(PRNG* rng){ + Key k; + k.key = rng->rand(); + return k; +} + +std::ostream& operator<<(std::ostream& os, const Key& k) { + os << k.key; + return os; +} diff --git a/src/misc.h b/src/misc.h index a2307fe..68d9e18 100644 --- a/src/misc.h +++ b/src/misc.h @@ -47,7 +47,7 @@ inline TimePoint now() { template struct HashTable { - Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; } + Entry* operator[](Key key) { return &table[key.get32() & (Size - 1)]; } private: std::vector table = std::vector(Size); diff --git a/src/position.cpp b/src/position.cpp index 6022518..17c5a10 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -134,24 +134,24 @@ void Position::init() { 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] = rng.rand(); + Zobrist::psq[c][pt][s] = Key::rand(&rng); for (File f = FILE_A; f <= FILE_H; ++f) - Zobrist::enpassant[f] = rng.rand(); + Zobrist::enpassant[f] = Key::rand(&rng); for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) { - Zobrist::castling[cr] = 0; + Zobrist::castling[cr].setzero(); Bitboard b = cr; while (b) { Key k = Zobrist::castling[1ULL << pop_lsb(&b)]; - Zobrist::castling[cr] ^= k ? k : rng.rand(); + Zobrist::castling[cr].xor_in(k.is_nonzero() ? k : Key::rand(&rng)); } } - Zobrist::side = rng.rand(); - Zobrist::exclusion = rng.rand(); + Zobrist::side = Key::rand(&rng); + Zobrist::exclusion = Key::rand(&rng); } @@ -318,7 +318,9 @@ void Position::set_castling_right(Color c, Square rfrom) { void Position::set_state(StateInfo* si) const { - si->key = si->pawnKey = si->materialKey = 0; + si->key.setzero(); + si->pawnKey.setzero(); + si->materialKey.setzero(); si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; si->psq = SCORE_ZERO; @@ -328,28 +330,28 @@ void Position::set_state(StateInfo* si) const { { Square s = pop_lsb(&b); Piece pc = piece_on(s); - si->key ^= Zobrist::psq[color_of(pc)][type_of(pc)][s]; + si->key.xor_in(Zobrist::psq[color_of(pc)][type_of(pc)][s]); si->psq += PSQT::psq[color_of(pc)][type_of(pc)][s]; } if (si->epSquare != SQ_NONE) - si->key ^= Zobrist::enpassant[file_of(si->epSquare)]; + si->key.xor_in(Zobrist::enpassant[file_of(si->epSquare)]); if (sideToMove == BLACK) - si->key ^= Zobrist::side; + si->key.xor_in(Zobrist::side); - si->key ^= Zobrist::castling[si->castlingRights]; + si->key.xor_in(Zobrist::castling[si->castlingRights]); for (Bitboard b = pieces(PAWN); b; ) { Square s = pop_lsb(&b); - si->pawnKey ^= Zobrist::psq[color_of(piece_on(s))][PAWN][s]; + si->pawnKey.xor_in(Zobrist::psq[color_of(piece_on(s))][PAWN][s]); } for (Color c = WHITE; c <= BLACK; ++c) for (PieceType pt = PAWN; pt <= KING; ++pt) for (int cnt = 0; cnt < pieceCount[c][pt]; ++cnt) - si->materialKey ^= Zobrist::psq[c][pt][cnt]; + si->materialKey.xor_in(Zobrist::psq[c][pt][cnt]); for (Color c = WHITE; c <= BLACK; ++c) for (PieceType pt = KNIGHT; pt <= QUEEN; ++pt) @@ -679,7 +681,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { captured = NO_PIECE_TYPE; st->psq += PSQT::psq[us][ROOK][rto] - PSQT::psq[us][ROOK][rfrom]; - k ^= Zobrist::psq[us][ROOK][rfrom] ^ Zobrist::psq[us][ROOK][rto]; + k.xor_in(Zobrist::psq[us][ROOK][rfrom] ^ Zobrist::psq[us][ROOK][rto]); } if (captured) @@ -703,7 +705,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { board[capsq] = NO_PIECE; // Not done by remove_piece() } - st->pawnKey ^= Zobrist::psq[them][PAWN][capsq]; + st->pawnKey.xor_in(Zobrist::psq[them][PAWN][capsq]); } else st->nonPawnMaterial[them] -= PieceValue[MG][captured]; @@ -712,8 +714,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { remove_piece(them, captured, capsq); // Update material hash key and prefetch access to materialTable - k ^= Zobrist::psq[them][captured][capsq]; - st->materialKey ^= Zobrist::psq[them][captured][pieceCount[them][captured]]; + k.xor_in(Zobrist::psq[them][captured][capsq]); + st->materialKey.xor_in(Zobrist::psq[them][captured][pieceCount[them][captured]]); prefetch(thisThread->materialTable[st->materialKey]); // Update incremental scores @@ -724,12 +726,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Update hash key - k ^= Zobrist::psq[us][pt][from] ^ Zobrist::psq[us][pt][to]; + k.xor_in(Zobrist::psq[us][pt][from] ^ Zobrist::psq[us][pt][to]); // Reset en passant square if (st->epSquare != SQ_NONE) { - k ^= Zobrist::enpassant[file_of(st->epSquare)]; + k.xor_in(Zobrist::enpassant[file_of(st->epSquare)]); st->epSquare = SQ_NONE; } @@ -737,7 +739,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) { int cr = castlingRightsMask[from] | castlingRightsMask[to]; - k ^= Zobrist::castling[st->castlingRights & cr]; + k.xor_in(Zobrist::castling[st->castlingRights & cr]); st->castlingRights &= ~cr; } @@ -753,7 +755,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { && (attacks_from(to - pawn_push(us), us) & pieces(them, PAWN))) { st->epSquare = (from + to) / 2; - k ^= Zobrist::enpassant[file_of(st->epSquare)]; + k.xor_in(Zobrist::enpassant[file_of(st->epSquare)]); } else if (type_of(m) == PROMOTION) @@ -767,10 +769,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { put_piece(us, promotion, to); // Update hash keys - k ^= Zobrist::psq[us][PAWN][to] ^ Zobrist::psq[us][promotion][to]; - st->pawnKey ^= Zobrist::psq[us][PAWN][to]; - st->materialKey ^= Zobrist::psq[us][promotion][pieceCount[us][promotion]-1] - ^ Zobrist::psq[us][PAWN][pieceCount[us][PAWN]]; + k.xor_in(Zobrist::psq[us][PAWN][to] ^ Zobrist::psq[us][promotion][to]); + st->pawnKey.xor_in(Zobrist::psq[us][PAWN][to]); + st->materialKey.xor_in(Zobrist::psq[us][promotion][pieceCount[us][promotion]-1] + ^ Zobrist::psq[us][PAWN][pieceCount[us][PAWN]]); // Update incremental score st->psq += PSQT::psq[us][promotion][to] - PSQT::psq[us][PAWN][to]; @@ -780,7 +782,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Update pawn hash key and prefetch access to pawnsTable - st->pawnKey ^= Zobrist::psq[us][PAWN][from] ^ Zobrist::psq[us][PAWN][to]; + st->pawnKey.xor_in(Zobrist::psq[us][PAWN][from] ^ Zobrist::psq[us][PAWN][to]); prefetch(thisThread->pawnsTable[st->pawnKey]); // Reset rule 50 draw counter @@ -902,11 +904,11 @@ void Position::do_null_move(StateInfo& newSt) { if (st->epSquare != SQ_NONE) { - st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->key.xor_in(Zobrist::enpassant[file_of(st->epSquare)]); st->epSquare = SQ_NONE; } - st->key ^= Zobrist::side; + st->key.xor_in(Zobrist::side); prefetch(TT.first_entry(st->key)); ++st->rule50; @@ -940,7 +942,7 @@ Key Position::key_after(Move m) const { Key k = st->key ^ Zobrist::side; if (captured) - k ^= Zobrist::psq[~us][captured][to]; + k.xor_in(Zobrist::psq[~us][captured][to]); return k ^ Zobrist::psq[us][pt][to] ^ Zobrist::psq[us][pt][from]; } diff --git a/src/search.cpp b/src/search.cpp index 1db84c7..eeb1efd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -93,7 +93,7 @@ namespace { void clear() { stableCnt = 0; - expectedPosKey = 0; + expectedPosKey.setzero(); pv[0] = pv[1] = pv[2] = MOVE_NONE; } diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 1b05db9..5954b7e 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -60,11 +60,11 @@ static uint64 calc_key(Position& pos, int mirror) color = !mirror ? WHITE : BLACK; for (pt = PAWN; pt <= KING; ++pt) for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - key ^= Zobrist::psq[WHITE][pt][i - 1]; + key ^= Zobrist::psq[WHITE][pt][i - 1].get_syzygy(); color = ~color; for (pt = PAWN; pt <= KING; ++pt) for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - key ^= Zobrist::psq[BLACK][pt][i - 1]; + key ^= Zobrist::psq[BLACK][pt][i - 1].get_syzygy(); return key; } @@ -83,11 +83,11 @@ static uint64 calc_key_from_pcs(int *pcs, int mirror) color = !mirror ? 0 : 8; for (pt = PAWN; pt <= KING; ++pt) for (i = 0; i < pcs[color + pt]; i++) - key ^= Zobrist::psq[WHITE][pt][i]; + key ^= Zobrist::psq[WHITE][pt][i].get_syzygy(); color ^= 8; for (pt = PAWN; pt <= KING; ++pt) for (i = 0; i < pcs[color + pt]; i++) - key ^= Zobrist::psq[BLACK][pt][i]; + key ^= Zobrist::psq[BLACK][pt][i].get_syzygy(); return key; } @@ -120,10 +120,11 @@ static int probe_wdl_table(Position& pos, int *success) int p[TBPIECES]; // Obtain the position's material signature key. - key = pos.material_key(); + key = pos.material_key().get_syzygy(); // Test for KvK. - if (key == (Zobrist::psq[WHITE][KING][0] ^ Zobrist::psq[BLACK][KING][0])) + if (key == (Zobrist::psq[WHITE][KING][0].get_syzygy() ^ + Zobrist::psq[BLACK][KING][0].get_syzygy())) return 0; ptr2 = TB_hash[key >> (64 - TBHASHBITS)]; @@ -220,7 +221,7 @@ static int probe_dtz_table(Position& pos, int wdl, int *success) int p[TBPIECES]; // Obtain the position's material signature key. - uint64 key = pos.material_key(); + uint64 key = pos.material_key().get_syzygy(); if (DTZ_table[0].key1 != key && DTZ_table[0].key2 != key) { for (i = 1; i < DTZ_ENTRIES; i++) diff --git a/src/tt.cpp b/src/tt.cpp index 151b71f..f273e07 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -31,10 +31,22 @@ TranspositionTable TT; // Our global transposition table /// measured in megabytes. Transposition table consists of a power of 2 number /// of clusters and each cluster consists of ClusterSize number of TTEntry. +/* + with TTEKey uint16_t then 10 bytes, 3 entries in a cluster = 30 + with TTEKey uint32_t then 12 bytes, 3 entries in a cluster = 36 + + 5000 megabytes, both choices lead to 2^27 clusters: + 2^27*30 = 3840 megabyte (note well, padding alignment turned off) + 2^27*36 = 4608 megabyte + + 48-bit TTEKey (not yet implemented) 2^27*48 = 5376 megabyte + though 37 (64-27) is the all the bits remaining if we stick with 64-bit keys + */ + void TranspositionTable::resize(size_t mbSize) { size_t newClusterCount = size_t(1) << msb((mbSize * 1024 * 1024) / sizeof(Cluster)); - + sync_cout << "newClusterCount " << newClusterCount << sync_endl; if (newClusterCount == clusterCount) return; @@ -74,15 +86,15 @@ void TranspositionTable::clear() { TTEntry* TranspositionTable::probe(const Key key, bool& found) const { TTEntry* const tte = first_entry(key); - const uint16_t key16 = key >> 48; // Use the high 16 bits as key inside the cluster + const TTEKey key16 = TTEKey(key); // Use the high 16 bits as key inside the cluster for (int i = 0; i < ClusterSize; ++i) - if (!tte[i].key16 || tte[i].key16 == key16) + if (!tte[i].key16.is_nonzero() || tte[i].key16 == key16) { - if ((tte[i].genBound8 & 0xFC) != generation8 && tte[i].key16) + if ((tte[i].genBound8 & 0xFC) != generation8 && tte[i].key16.is_nonzero()) tte[i].genBound8 = uint8_t(generation8 | tte[i].bound()); // Refresh - return found = (bool)tte[i].key16, &tte[i]; + return found = tte[i].key16.is_nonzero(), &tte[i]; } // Find an entry to be replaced according to the replacement strategy diff --git a/src/tt.h b/src/tt.h index 70283fc..0e6c0c5 100644 --- a/src/tt.h +++ b/src/tt.h @@ -45,16 +45,16 @@ struct TTEntry { void save(Key k, Value v, Bound b, Depth d, Move m, Value ev, uint8_t g) { // Preserve any existing move for the same position - if (m || (k >> 48) != key16) + if (m || (TTEKey(k) != key16)) move16 = (uint16_t)m; // Don't overwrite more valuable entries - if ( (k >> 48) != key16 + if ( (TTEKey(k) != key16) || d > depth8 - 4 /* || g != (genBound8 & 0xFC) // Matching non-zero keys are already refreshed by probe() */ || b == BOUND_EXACT) { - key16 = (uint16_t)(k >> 48); + key16 = TTEKey(k); value16 = (int16_t)v; eval16 = (int16_t)ev; genBound8 = (uint8_t)(g | b); @@ -65,7 +65,7 @@ struct TTEntry { private: friend class TranspositionTable; - uint16_t key16; + TTEKey key16; uint16_t move16; int16_t value16; int16_t eval16; @@ -88,10 +88,11 @@ class TranspositionTable { struct Cluster { TTEntry entry[ClusterSize]; - char padding[2]; // Align to a divisor of the cache line size + //char padding[2]; // Align to a divisor of the cache line size }; - static_assert(CacheLineSize % sizeof(Cluster) == 0, "Cluster size incorrect"); + //static_assert(CacheLineSize % sizeof(Cluster) == 0, "Cluster size incorrect"); + static_assert(CacheLineSize >= sizeof(Cluster), "Cluster size too large"); public: ~TranspositionTable() { free(mem); } @@ -104,7 +105,7 @@ public: // The lowest order bits of the key are used to get the index of the cluster TTEntry* first_entry(const Key key) const { - return &table[(size_t)key & (clusterCount - 1)].entry[0]; + return &table[key.getsize_t() & (clusterCount - 1)].entry[0]; } private: diff --git a/src/types.h b/src/types.h index 10dfdb0..4eb0603 100644 --- a/src/types.h +++ b/src/types.h @@ -43,6 +43,7 @@ #include #include #include +#include #if defined(_MSC_VER) // Disable some silly and noisy warning from MSVC compiler @@ -97,7 +98,59 @@ const bool Is64Bit = true; const bool Is64Bit = false; #endif -typedef uint64_t Key; +class PRNG; +class TTEKey; + +class Key { + uint64_t key; + friend bool operator<(const Key& x, const Key& y){ + return x.key < y.key; + } + friend bool operator==(const Key& x, const Key& y){ + return x.key == y.key; + } + friend Key operator^(const Key& x, const Key& y){ + Key out; + out.key = x.key ^ y.key; + return out; + } + friend std::ostream& operator<<(std::ostream&, const Key&); + public: + + Key(const Key& copy) : key(copy.key) {} + Key() { + // this constructor is sketchy because key is undefined + } + void setzero() { key = 0; } + + //Unfortunately, cannot make the just constructor + //TTEKey::TTEKey(Key) friend and still have that constructor inline + //because of mutual dependencies. Key needs to see into the guts of + //TTEKey (to declare the friend), and TTEKey needs to see into guts + //of Key to implement the constructor. We want inline for + //performance. + friend class TTEKey; + uint32_t get32() const { + return (uint32_t)key; + } + size_t getsize_t() const { + //little bit worried collisions when using only 64 bits in the + //transposition table + return (size_t) key; + } + void xor_in(const Key& in){ + key ^= in.key; + } + uint64_t get_syzygy() const { + //syzygy needs a 64-bit key + return key; + } + bool is_nonzero() const { + return key!=0; + } + static Key rand(PRNG* rng); +}; + typedef uint64_t Bitboard; const int MAX_MOVES = 256; @@ -425,4 +478,26 @@ inline bool is_ok(Move m) { return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE } +// key as stored in a transposition table entry +class TTEKey { + uint32_t key; + // this is where the wider key size gets used + public: + // the widest permitted value is ((8-sizeof(key))*8) = 32 + // 48 replicates the original 16-bit behavior (confirmed) + TTEKey(const Key& k) : key(k.key >> 32 ) { + } + friend bool operator!=(const TTEKey& x, const TTEKey& y){ + return x.key != y.key; + } + friend bool operator==(const TTEKey& x, const TTEKey& y){ + return x.key == y.key; + } + bool is_nonzero() const { + return key; + } + +}; + + #endif // #ifndef TYPES_H_INCLUDED