add hash.c: zobrist init + pos zobrist calc + tt hash creation
This commit is contained in:
235
src/hash.c
Normal file
235
src/hash.c
Normal file
@@ -0,0 +1,235 @@
|
||||
/* hash.c - hash management.
|
||||
*
|
||||
* Copyright (C) 2024 Bruno Raoult ("br")
|
||||
* Licensed under the GNU General Public License v3.0 or later.
|
||||
* Some rights reserved. See COPYING.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this
|
||||
* program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <brlib.h>
|
||||
#include <bitops.h>
|
||||
|
||||
#include "chessdefs.h"
|
||||
#include "util.h"
|
||||
#include "position.h"
|
||||
#include "piece.h"
|
||||
#include "hash.h"
|
||||
|
||||
u64 zobrist_pieces[16][64];
|
||||
u64 zobrist_castling[4 * 4 + 1];
|
||||
u64 zobrist_turn; /* for black, XOR each ply */
|
||||
u64 zobrist_ep[9]; /* 0-7: ep file, 8: SQUARE_NONE */
|
||||
|
||||
hasht_t hash_tt; /* main transposition table */
|
||||
|
||||
/**
|
||||
* zobrist_init() - initialize zobrist tables.
|
||||
*
|
||||
* Initialize all zobrist random bitmasks.
|
||||
*/
|
||||
void zobrist_init(void)
|
||||
{
|
||||
for (color_t c = WHITE; c <= BLACK; ++c) {
|
||||
for (piece_type_t p = PAWN; p <= KING; ++p)
|
||||
for (square_t sq = A1; sq <= H8; ++sq)
|
||||
zobrist_pieces[MAKE_PIECE(p, c)][sq] = rand64();
|
||||
}
|
||||
for (castle_rights_t c = CASTLE_NONE; c <= CASTLE_ALL; ++c)
|
||||
zobrist_castling[c] = rand64();
|
||||
for (file_t f = FILE_A; f <= FILE_H; ++f)
|
||||
zobrist_ep[f] = rand64();
|
||||
zobrist_ep[8] = 0;
|
||||
zobrist_turn = rand64();
|
||||
}
|
||||
|
||||
/**
|
||||
* zobrist_calc() - calculate a position zobrist hash.
|
||||
*
|
||||
* Normally, Zobrist keys are incrementally calculated when doing or
|
||||
* undoing a move.
|
||||
* This function should normally only be called:
|
||||
* - When starting a new position
|
||||
* - To verify incremental Zobrist calculation is correct
|
||||
*
|
||||
*/
|
||||
key zobrist_calc(pos_t *pos)
|
||||
{
|
||||
key key = 0;
|
||||
|
||||
if (pos->turn == BLACK)
|
||||
key ^= zobrist_turn;
|
||||
|
||||
for (color_t c = WHITE; c <= BLACK; ++c) {
|
||||
for (piece_type_t pt = PAWN; pt <= KING; ++pt) {
|
||||
piece_t piece = MAKE_PIECE(pt, c);
|
||||
bitboard_t bb = pos->bb[c][pt];
|
||||
while (bb) {
|
||||
square_t sq = bb_next(&bb);
|
||||
key ^= zobrist_pieces[piece][sq];
|
||||
}
|
||||
}
|
||||
}
|
||||
key ^= pos->castle;
|
||||
key ^= EP_ZOBRIST_IDX(pos->en_passant);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* hash_create() - hashtable creation.
|
||||
* @sizemb: s32 size of hash table in Mb
|
||||
*
|
||||
* Create a hash table of max @sizemb (or HASH_SIZE_MBif @sizemb <= 0) Mb size.
|
||||
* This function must be called at startup.
|
||||
*
|
||||
* The number of bucket_t entries fitting in @sizemb is calculated, and rounded
|
||||
* (down) to a power of 2.
|
||||
* This means the actual size could be lower than @sizemb (nearly halved in
|
||||
* worst case).
|
||||
*
|
||||
* If transposition hashtable already exists and new size would not change,
|
||||
* the old one is cleared.
|
||||
* If transposition hashtable already exists and new size is different,
|
||||
* the old one is destroyed first (old data is not preserved).
|
||||
*
|
||||
* TODO:
|
||||
* - Rebuild old hashtable data ?
|
||||
*
|
||||
* @return: hash table size in Mb. If memory allocation fails, the function does
|
||||
* not return.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <locale.h>
|
||||
|
||||
static void zob(char *str, size_t alloc)
|
||||
{
|
||||
extern char etext, edata, end;
|
||||
printf("%s %lu ", str, alloc);
|
||||
//printf(" program text (etext) %10p\n", &etext);
|
||||
//printf(" initialized data (edata) %10p\n", &edata);
|
||||
//printf(" uninitialized data (end) %10p\n", &end);
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
printf("\n\tpress a key...");
|
||||
getchar();
|
||||
}
|
||||
|
||||
int hash_create(s32 sizemb)
|
||||
{
|
||||
size_t bytes, target_nbuckets;
|
||||
u32 nbits;
|
||||
|
||||
static_assert(sizeof(hentry_t) == 16, "fatal: hentry_t size != 16");
|
||||
|
||||
printf("mb = %'7u ", sizemb);
|
||||
/* adjust tt size */
|
||||
if (sizemb <= 0)
|
||||
sizemb = HASH_SIZE_DEFAULT;
|
||||
sizemb = clamp(sizemb, HASH_SIZE_MIN, HASH_SIZE_MAX);
|
||||
printf("-> %'6d ", sizemb);
|
||||
|
||||
bytes = sizemb * 1024ull * 1024ull; /* bytes wanted */
|
||||
target_nbuckets = bytes / sizeof(bucket_t); /* target buckets */
|
||||
|
||||
nbits = msb64(target_nbuckets); /* adjust to power of 2 */
|
||||
|
||||
if (hash_tt.nbits != nbits) {
|
||||
if (hash_tt.nbits)
|
||||
hash_delete();
|
||||
|
||||
hash_tt.nbits = nbits;
|
||||
|
||||
hash_tt.nbuckets = BIT(hash_tt.nbits);
|
||||
hash_tt.nkeys = hash_tt.nbuckets * NBUCKETS;
|
||||
|
||||
hash_tt.bytes = hash_tt.nbuckets * sizeof(bucket_t);
|
||||
hash_tt.mb = hash_tt.bytes / 1024 / 1024;
|
||||
|
||||
hash_tt.mask = -1ull >> (64 - nbits);
|
||||
|
||||
hash_tt.keys = safe_malloc(hash_tt.bytes);
|
||||
|
||||
//printf("bits=%2d size=%'15lu/%'6d Mb/%'14lu buckets ",
|
||||
// hash_tt.nbits, hash_tt.bytes, hash_tt.mb, hash_tt.nbuckets);
|
||||
//printf("mask=%9x\n", hash_tt.mask);
|
||||
} else {
|
||||
printf("unchanged (cleared)\n");
|
||||
//hash_clear();
|
||||
}
|
||||
/* attention - may fail ! */
|
||||
hash_clear();
|
||||
|
||||
return hash_tt.nbits;
|
||||
}
|
||||
|
||||
/**
|
||||
* hash_clear() - clear hashtable data.
|
||||
*
|
||||
* Reset hashtable entries (if available) and statistic information.
|
||||
*/
|
||||
void hash_clear()
|
||||
{
|
||||
if (hash_tt.keys)
|
||||
memset(hash_tt.keys, 0, hash_tt.bytes);
|
||||
|
||||
hash_tt.used_buckets = 0;
|
||||
hash_tt.used_keys = 0;
|
||||
hash_tt.collisions = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hash_delete() - delete hashtable data.
|
||||
*
|
||||
* free hashtable data.
|
||||
*/
|
||||
void hash_delete()
|
||||
{
|
||||
if (hash_tt.keys)
|
||||
safe_free(hash_tt.keys);
|
||||
memset(&hash_tt, 0, sizeof(hash_tt));
|
||||
}
|
||||
|
||||
// #define zob()
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("entry=%lu bucket=%lu\n", sizeof(hentry_t), sizeof(bucket_t));
|
||||
zob("before alloc", 0);
|
||||
setlocale(LC_NUMERIC, "");
|
||||
hash_create(-1);
|
||||
zob("after", -1);
|
||||
hash_create(1);
|
||||
hash_create(2);
|
||||
hash_create(3);
|
||||
hash_create(4);
|
||||
hash_create(10);
|
||||
zob("after", 10);
|
||||
hash_create(20);
|
||||
hash_create(40);
|
||||
hash_create(100);
|
||||
zob("after", 100);
|
||||
hash_create(200);
|
||||
hash_create(400);
|
||||
hash_create(1000);
|
||||
zob("after", 1000);
|
||||
hash_create(1000);
|
||||
zob("after", 1024);
|
||||
//hash_create(2000);
|
||||
//zob("after", 100);
|
||||
hash_create(4096);
|
||||
zob("after", 4096);
|
||||
//hash_create(32767);
|
||||
//hash_create(32768);
|
||||
//hash_create(32769);
|
||||
//hash_create(65536);
|
||||
//hash_create(400000);
|
||||
return 0;
|
||||
}
|
89
src/hash.h
Normal file
89
src/hash.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/* hash.h - hash management.
|
||||
*
|
||||
* Copyright (C) 2024 Bruno Raoult ("br")
|
||||
* Licensed under the GNU General Public License v3.0 or later.
|
||||
* Some rights reserved. See COPYING.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this
|
||||
* program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef HASH_H
|
||||
#define HASH_H
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "chessdefs.h"
|
||||
|
||||
#define NBUCKETS 4 /* buckets per hash table entry */
|
||||
|
||||
#define HASH_SIZE_DEFAULT 32 /* default: 32Mb */
|
||||
#define HASH_SIZE_MIN 4
|
||||
#define HASH_SIZE_MAX 32768 /* 32Gb */
|
||||
|
||||
typedef u64 key;
|
||||
|
||||
/**
|
||||
* hentry_t: hashtable bucket.
|
||||
*
|
||||
* Size should be exactly 16 bytes. If impossible to fit necessary data in
|
||||
* 16 bytes in future, it should be updated to be exactly 32 bytes.
|
||||
*/
|
||||
typedef struct {
|
||||
key key; /* zobrist */
|
||||
union {
|
||||
u64 data;
|
||||
struct {
|
||||
u16 depth; /* ply in search */
|
||||
s16 eval;
|
||||
u16 move;
|
||||
u8 flags; /* maybe for locking, etc... */
|
||||
u8 filler;
|
||||
};
|
||||
};
|
||||
} hentry_t;
|
||||
|
||||
typedef struct {
|
||||
hentry_t buckets[NBUCKETS];
|
||||
} bucket_t;
|
||||
|
||||
typedef struct {
|
||||
bucket_t *keys; /* &hashtable entries */
|
||||
|
||||
/* memory size in bytes/mb */
|
||||
size_t bytes;
|
||||
u32 mb;
|
||||
|
||||
/* size in buckets/keys */
|
||||
size_t nbuckets;
|
||||
size_t nkeys; /* nbuckets * NBUCKETS */
|
||||
|
||||
/* internal representation */
|
||||
u32 nbits; /* #buckets in bits, power of 2 */
|
||||
u32 mask; /* nbuckets - 1, key mask */
|
||||
|
||||
/* stats - unsure about usage */
|
||||
size_t used_buckets;
|
||||
size_t used_keys;
|
||||
u64 collisions;
|
||||
} hasht_t;
|
||||
|
||||
/* hack:
|
||||
* ep zobrist key index is 0-7 for each en-passant file, 8 for SQUARE_NONE.
|
||||
* To transform :
|
||||
* - ep == 64 (SQUARE_NONE) to id = 8
|
||||
* - ep == 0~63 to idx = sq_file(ep), i.e. (ep & 7)
|
||||
* we use the formula:
|
||||
* idx = ( ( ep & SQUARE_NONE ) >> 3 ) | sq_file(ep);
|
||||
*/
|
||||
#define EP_ZOBRIST_IDX(ep) ( ( (ep) >> 3 ) | sq_file(ep) )
|
||||
|
||||
void zobrist_init(void);
|
||||
int hash_create(int Mb);
|
||||
void hash_clear();
|
||||
void hash_delete();
|
||||
|
||||
#endif /* HASH_H */
|
Reference in New Issue
Block a user