add hash.c: zobrist init + pos zobrist calc + tt hash creation

This commit is contained in:
2024-05-14 11:40:44 +02:00
parent 7baf66f1b6
commit 038f6b5669
2 changed files with 324 additions and 0 deletions

235
src/hash.c Normal file
View 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
View 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 */