Subversion Repositories Games.Chess Giants

Rev

Rev 108 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
33 pmbaty 1
#include "chess.h"
2
#include "data.h"
154 pmbaty 3
/* last modified 08/28/16 */
33 pmbaty 4
/*
5
 *******************************************************************************
6
 *                                                                             *
7
 *   HashProbe() is used to retrieve entries from the transposition table so   *
8
 *   this sub-tree won't have to be searched again if we reach a position that *
9
 *   has been searched previously.  A transposition table position contains    *
10
 *   the following data packed into 128 bits with each item taking the number  *
11
 *   of bits given in the table below:                                         *
12
 *                                                                             *
108 pmbaty 13
 *     shr  bits   name   description                                          *
14
 *      55    9    age    search id to identify old trans/ref entries.         *
15
 *      53    2    type   0->value is worthless; 1-> value represents a        *
33 pmbaty 16
 *                        fail-low bound; 2-> value represents a fail-high     *
17
 *                        bound; 3-> value is an exact score.                  *
108 pmbaty 18
 *      32   21    move   best move from the current position, according to    *
33 pmbaty 19
 *                        the search at the time this position was stored.     *
108 pmbaty 20
 *      17   15    draft  the depth of the search below this position, which   *
33 pmbaty 21
 *                        is used to see if we can use this entry at the       *
22
 *                        current position.                                    *
108 pmbaty 23
 *       0   17    value  unsigned integer value of this position + 65536.     *
33 pmbaty 24
 *                        this might be a good score or search bound.          *
108 pmbaty 25
 *       0   64    key    64 bit hash signature, used to verify that this      *
33 pmbaty 26
 *                        entry goes with the current board position.          *
27
 *                                                                             *
28
 *   The underlying scheme here is that we use a "bucket" of N entries.  In    *
29
 *   HashProbe() we simply compare against each of the four entries for a      *
30
 *   match.  Each "bucket" is carefully aligned to a 64-byte boundary so that  *
31
 *   the bucket fits into a single cache line for efficiency.  The bucket size *
32
 *   (N) is currently set to 4.                                                *
33
 *                                                                             *
34
 *   Crafty uses the lockless hashing approach to avoid lock overhead in the   *
35
 *   hash table accessing (reading or writing).  What we do is store the key   *
36
 *   and the information in two successive writes to memory.  But since there  *
37
 *   is nothing that prevents another CPU from interlacing its writes with     *
38
 *   ours, we want to make sure that the bound/draft/etc really goes with the  *
39
 *   key.  Consider thread 1 trying to store A1 and A2 in two successive 64    *
40
 *   words, while thread 2 is trying to store B1 and B2.  Since the two cpus   *
41
 *   are fully independent, we could end up with {A1,A2}, {A1,B2}, {B1,A2} or  *
42
 *   {B1,B2}.  The two cases with one word of entry A and one word of entry B  *
43
 *   are problematic since the information part does not belong with the       *
44
 *   signature part, and a hash hit (signature match) will retrieve data that  *
45
 *   does not match the position.  Let's assume that the first word is the     *
46
 *   signature (A1 or B1) and the second word is the data (A2 or B2).  What we *
47
 *   do is store A1^A2 (exclusive-or the two parts) in the 1 (key) slot of the *
48
 *   entry, and store A2 in the data part.  Now, before we try to compare the  *
49
 *   signatures, we have to "un-corrupt" the stored signature by again using   *
50
 *   xor, since A1^A2^A2 gives us the original A1 signature again.  But if we  *
51
 *   store A1^A2, and the data part gets replaced by B2, then we try to match  *
52
 *   against A1^A2^B2 and that won't match unless we are lucky and A2 == B2    *
53
 *   which means the match is OK anyway.  This eliminates the need to lock the *
54
 *   hash table while storing the two values, which would be a big performance *
55
 *   hit since hash entries are probed/stored in almost every node of the tree *
56
 *   except for the quiescence search.                                         *
57
 *                                                                             *
58
 *******************************************************************************
59
 */
60
int HashProbe(TREE * RESTRICT tree, int ply, int depth, int side, int alpha,
61
    int beta, int *value) {
62
  HASH_ENTRY *htable;
63
  HPATH_ENTRY *ptable;
64
  uint64_t word1, word2, temp_hashkey;
108 pmbaty 65
  int type, draft, avoid_null = 0, val, entry, i;
33 pmbaty 66
 
67
/*
68
 ************************************************************
69
 *                                                          *
70
 *  All we have to do is loop through four entries to see   *
71
 *  if there is a signature match.  There can only be one   *
72
 *  instance of any single signature, so the first match is *
73
 *  all we need.                                            *
74
 *                                                          *
75
 ************************************************************
76
 */
77
  tree->hash_move[ply] = 0;
78
  temp_hashkey = (side) ? HashKey : ~HashKey;
108 pmbaty 79
  htable = hash_table + (temp_hashkey & hash_mask);
80
  for (entry = 0; entry < 4; entry++) {
81
    word1 = htable[entry].word1;
82
    word2 = htable[entry].word2 ^ word1;
33 pmbaty 83
    if (word2 == temp_hashkey)
84
      break;
85
  }
86
/*
87
 ************************************************************
88
 *                                                          *
89
 *  If we found a match, we have to verify that the draft   *
90
 *  is at least equal to the current depth, if not higher,  *
91
 *  and that the bound/score will let us terminate the      *
92
 *  search early.                                           *
93
 *                                                          *
94
 *  We also return an "avoid_null" status if the matched    *
95
 *  entry does not have enough draft to terminate the       *
96
 *  current search but does have enough draft to prove that *
97
 *  a null-move search would not fail high.  This avoids    *
98
 *  the null-move search overhead in positions where it is  *
99
 *  simply a waste of time to try it.                       *
100
 *                                                          *
101
 *  If this is an EXACT entry, we are going to store the PV *
102
 *  in a safe place so that if we get a hit on this entry,  *
103
 *  we can recover the PV and see the complete path rather  *
104
 *  rather than one that is incomplete.                     *
105
 *                                                          *
106
 *  One other issue is to update the age field if we get a  *
107
 *  hit on an old position, so that it won't be replaced    *
108
 *  just because it came from a previous search.            *
109
 *                                                          *
110
 ************************************************************
111
 */
112
  if (entry < 4) {
113
    val = (word1 & 0x1ffff) - 65536;
114
    draft = (word1 >> 17) & 0x7fff;
115
    tree->hash_move[ply] = (word1 >> 32) & 0x1fffff;
116
    type = (word1 >> 53) & 3;
108 pmbaty 117
    if ((type & UPPER) &&
118
        depth - null_depth - depth / null_divisor - 1 <= draft && val < beta)
33 pmbaty 119
      avoid_null = AVOID_NULL_MOVE;
120
    if (depth <= draft) {
121
      if (val > 32000)
122
        val -= ply - 1;
123
      else if (val < -32000)
124
        val += ply - 1;
125
      *value = val;
126
/*
127
 ************************************************************
128
 *                                                          *
129
 *  We have three types of results.  An EXACT entry was     *
130
 *  stored when val > alpha and val < beta, and represents  *
131
 *  an exact score.  An UPPER entry was stored when val <   *
132
 *  alpha, which represents an upper bound with the score   *
133
 *  likely being even lower.  A LOWER entry was stored when *
134
 *  val > beta, which represents alower bound with the      *
135
 *  score likely being even higher.                         *
136
 *                                                          *
137
 *  For EXACT entries, we save the path from the position   *
138
 *  to the terminal node that produced the backed-up score  *
139
 *  so that we can complete the PV if we get a hash hit on  *
140
 *  this entry.                                             *
141
 *                                                          *
142
 ************************************************************
143
 */
144
      switch (type) {
145
        case EXACT:
146
          if (val > alpha && val < beta) {
154 pmbaty 147
            if (word1 >> 55 != transposition_age) {
148
              word1 = (word1 & 0x007fffffffffffffull) | ((uint64_t)
149
                  transposition_age << 55);
150
              htable[entry].word1 = word1;
151
              htable[entry].word2 = word1 ^ word2;
152
            }
108 pmbaty 153
            SavePV(tree, ply, 1);
33 pmbaty 154
            ptable = hash_path + (temp_hashkey & hash_path_mask);
108 pmbaty 155
            for (entry = 0; entry < 16; entry++)
156
              if (ptable[entry].path_sig == temp_hashkey) {
157
                for (i = ply;
158
                    i < Min(MAXPLY - 1, ptable[entry].hash_pathl + ply); i++)
159
                  tree->pv[ply - 1].path[i] =
160
                      ptable[entry].hash_path_moves[i - ply];
161
                if (ptable[entry].hash_pathl + ply < MAXPLY - 1)
33 pmbaty 162
                  tree->pv[ply - 1].pathh = 0;
163
                tree->pv[ply - 1].pathl =
108 pmbaty 164
                    Min(MAXPLY - 1, ply + ptable[entry].hash_pathl);
165
                ptable[entry].hash_path_age = transposition_age;
33 pmbaty 166
                break;
167
              }
168
          }
169
          return HASH_HIT;
170
        case UPPER:
154 pmbaty 171
          if (val <= alpha) {
172
            if (word1 >> 55 != transposition_age) {
173
              word1 = (word1 & 0x007fffffffffffffull) | ((uint64_t)
174
                  transposition_age << 55);
175
              htable[entry].word1 = word1;
176
              htable[entry].word2 = word1 ^ word2;
177
            }
33 pmbaty 178
            return HASH_HIT;
154 pmbaty 179
          }
33 pmbaty 180
          break;
181
        case LOWER:
154 pmbaty 182
          if (val >= beta) {
183
            if (word1 >> 55 != transposition_age) {
184
              word1 = (word1 & 0x007fffffffffffffull) | ((uint64_t)
185
                  transposition_age << 55);
186
              htable[entry].word1 = word1;
187
              htable[entry].word2 = word1 ^ word2;
188
            }
33 pmbaty 189
            return HASH_HIT;
154 pmbaty 190
          }
33 pmbaty 191
          break;
192
      }
193
    }
194
    return avoid_null;
195
  }
196
  return HASH_MISS;
197
}
198
 
154 pmbaty 199
/* last modified 02/12/16 */
33 pmbaty 200
/*
201
 *******************************************************************************
202
 *                                                                             *
203
 *   HashStore() is used to store entries into the transposition table so that *
204
 *   this sub-tree won't have to be searched again if the same position is     *
205
 *   reached.  We basically store three types of entries:                      *
206
 *                                                                             *
207
 *     (1) EXACT.  This entry is stored when we complete a search at some ply  *
108 pmbaty 208
 *          and end up with a score that is greater than alpha and less than   *
209
 *          beta, which is an exact score, which also has a best move to try   *
210
 *          if we encounter this position again.                               *
33 pmbaty 211
 *                                                                             *
212
 *     (2) LOWER.  This entry is stored when we complete a search at some ply  *
108 pmbaty 213
 *          and end up with a score that is greater than or equal to beta.  We *
214
 *          know know that this score should be at least equal to beta and may *
215
 *          well be even higher.  So this entry represents a lower bound on    *
216
 *          the score for this node, and we also have a good move to try since *
217
 *          it caused the cutoff, although we do not know if it is the best    *
218
 *          move or not since not all moves were search.                       *
33 pmbaty 219
 *                                                                             *
220
 *     (3) UPPER.  This entry is stored when we complete a search at some ply  *
108 pmbaty 221
 *          and end up with a score that is less than or equal to alpha.  We   *
222
 *          know know that this score should be at least equal to alpha and    *
223
 *          may well be even lower.  So this entry represents an upper bound   *
224
 *          on the score for this node.  We have no idea about which move is   *
225
 *          best in this position since they all failed low, so we store a     *
226
 *          best move of zero.                                                 *
33 pmbaty 227
 *                                                                             *
228
 *   For storing, we may require three passes.  We make our first pass looking *
229
 *   for an entry that matches the current hash signature.  If we find a match *
230
 *   then we are constrained to overwrite that entry regardless of any other   *
231
 *   considerations.  The second pass looks for entries stored in previous     *
232
 *   searches (not iterations) and chooses the one with the shallowest draft,  *
233
 *   if one is found;  Otherwise we make a final pass over the bucket and      *
234
 *   choose the entry with the shallowest draft, period.                       *
235
 *                                                                             *
236
 *******************************************************************************
237
 */
238
void HashStore(TREE * RESTRICT tree, int ply, int depth, int side, int type,
239
    int value, int bestmove) {
240
  HASH_ENTRY *htable, *replace = 0;
241
  HPATH_ENTRY *ptable;
242
  uint64_t word1, temp_hashkey;
243
  int entry, draft, age, replace_draft, i, j;
244
 
245
/*
246
 ************************************************************
247
 *                                                          *
248
 *  "Fill in the blank" and build a table entry from        *
249
 *  current search information.                             *
250
 *                                                          *
251
 ************************************************************
252
 */
253
  word1 = transposition_age;
254
  word1 = (word1 << 2) | type;
255
  if (value > 32000)
256
    value += ply - 1;
257
  else if (value < -32000)
258
    value -= ply - 1;
259
  word1 = (word1 << 21) | bestmove;
260
  word1 = (word1 << 15) | depth;
261
  word1 = (word1 << 17) | (value + 65536);
262
  temp_hashkey = (side) ? HashKey : ~HashKey;
263
/*
264
 ************************************************************
265
 *                                                          *
266
 *  Now we search for an entry to overwrite in three        *
267
 *  passes.                                                 *
268
 *                                                          *
269
 *  Pass 1:  If any signature in the table matches the      *
270
 *    current signature, we are going to overwrite this     *
271
 *    entry, period.  It might seem worthwhile to check the *
272
 *    draft and not overwrite if the table draft is greater *
273
 *    than the current remaining depth, but after you think *
274
 *    about it, this is a bad idea.  If the draft is        *
275
 *    greater than or equal the current remaining depth,    *
276
 *    then we should never get here unless the stored bound *
277
 *    or score is unusable because of the current alpha/    *
278
 *    beta window.  So we are overwriting to avoid losing   *
279
 *    the current result.                                   *
280
 *                                                          *
281
 *  Pass 2:  If any of the entries come from a previous     *
282
 *    search (not iteration) then we choose the entry from  *
283
 *    this set that has the smallest draft, since it is the *
284
 *    least potentially usable result.                      *
285
 *                                                          *
286
 *  Pass 3:  If neither of the above two found an entry to  *
287
 *    overwrite, we simply choose the entry from the bucket *
288
 *    with the smallest draft and overwrite that.           *
289
 *                                                          *
290
 ************************************************************
291
 */
108 pmbaty 292
  htable = hash_table + (temp_hashkey & hash_mask);
293
  for (entry = 0; entry < 4; entry++) {
294
    if (temp_hashkey == (htable[entry].word1 ^ htable[entry].word2)) {
295
      replace = htable + entry;
33 pmbaty 296
      break;
297
    }
298
  }
299
  if (!replace) {
300
    replace_draft = 99999;
108 pmbaty 301
    for (entry = 0; entry < 4; entry++) {
302
      age = htable[entry].word1 >> 55;
303
      draft = (htable[entry].word1 >> 17) & 0x7fff;
33 pmbaty 304
      if (age != transposition_age && replace_draft > draft) {
108 pmbaty 305
        replace = htable + entry;
33 pmbaty 306
        replace_draft = draft;
307
      }
308
    }
309
    if (!replace) {
108 pmbaty 310
      for (entry = 0; entry < 4; entry++) {
311
        draft = (htable[entry].word1 >> 17) & 0x7fff;
33 pmbaty 312
        if (replace_draft > draft) {
108 pmbaty 313
          replace = htable + entry;
33 pmbaty 314
          replace_draft = draft;
315
        }
316
      }
317
    }
318
  }
319
/*
320
 ************************************************************
321
 *                                                          *
322
 *  Now that we know which entry to replace, we simply      *
323
 *  stuff the values and exit.  Note that the two 64 bit    *
324
 *  words are xor'ed together and stored as the signature   *
325
 *  for the "lockless-hash" approach.                       *
326
 *                                                          *
327
 ************************************************************
328
 */
329
  replace->word1 = word1;
330
  replace->word2 = temp_hashkey ^ word1;
331
/*
332
 ************************************************************
333
 *                                                          *
334
 *  If this is an EXACT entry, we are going to store the PV *
335
 *  in a safe place so that if we get a hit on this entry,  *
336
 *  we can recover the PV and see the complete path rather  *
337
 *  rather than one that is incomplete.                     *
338
 *                                                          *
339
 ************************************************************
340
 */
341
  if (type == EXACT) {
342
    ptable = hash_path + (temp_hashkey & hash_path_mask);
343
    for (i = 0; i < 16; i++, ptable++) {
344
      if (ptable->path_sig == temp_hashkey ||
154 pmbaty 345
          transposition_age != ptable->hash_path_age) {
33 pmbaty 346
        for (j = ply; j < tree->pv[ply - 1].pathl; j++)
347
          ptable->hash_path_moves[j - ply] = tree->pv[ply - 1].path[j];
348
        ptable->hash_pathl = tree->pv[ply - 1].pathl - ply;
349
        ptable->path_sig = temp_hashkey;
350
        ptable->hash_path_age = transposition_age;
351
        break;
352
      }
353
    }
354
  }
355
}
356
 
108 pmbaty 357
/* last modified 09/16/14 */
33 pmbaty 358
/*
359
 *******************************************************************************
360
 *                                                                             *
361
 *   HashStorePV() is called by Iterate() to insert the PV moves so they will  *
362
 *   be searched before any other moves.  Normally the PV moves would be in    *
363
 *   the table, but on occasion they can be overwritten, particularly the ones *
364
 *   that are a significant distance from the root since those table entries   *
365
 *   will have a low draft.                                                    *
366
 *                                                                             *
367
 *******************************************************************************
368
 */
369
void HashStorePV(TREE * RESTRICT tree, int side, int ply) {
370
  HASH_ENTRY *htable, *replace;
371
  uint64_t temp_hashkey, word1;
372
  int entry, draft, replace_draft, age;
373
 
374
/*
375
 ************************************************************
376
 *                                                          *
377
 *  First, compute the initial hash address and the fake    *
378
 *  entry we will store if we don't find a valid match      *
379
 *  already in the table.                                   *
380
 *                                                          *
381
 ************************************************************
382
 */
383
  temp_hashkey = (side) ? HashKey : ~HashKey;
384
  word1 = transposition_age;
385
  word1 = (word1 << 2) | WORTHLESS;
386
  word1 = (word1 << 21) | tree->pv[0].path[ply];
387
  word1 = (word1 << 32) | 65536;
388
/*
389
 ************************************************************
390
 *                                                          *
391
 *  Now we search for an entry to overwrite in three        *
392
 *  passes.                                                 *
393
 *                                                          *
394
 *  Pass 1:  If any signature in the table matches the      *
395
 *    current signature, we are going to overwrite this     *
396
 *    entry, period.  It might seem worthwhile to check the *
397
 *    draft and not overwrite if the table draft is greater *
398
 *    than the current remaining depth, but after you think *
399
 *    about it, this is a bad idea.  If the draft is        *
400
 *    greater than or equal the current remaining depth,    *
401
 *    then we should never get here unless the stored bound *
402
 *    or score is unusable because of the current alpha/    *
403
 *    beta window.  So we are overwriting to avoid losing   *
404
 *    the current result.                                   *
405
 *                                                          *
406
 *  Pass 2:  If any of the entries come from a previous     *
407
 *    search (not iteration) then we choose the entry from  *
408
 *    this set that has the smallest draft, since it is the *
409
 *    least potentially usable result.                      *
410
 *                                                          *
411
 *  Pass 3:  If neither of the above two found an entry to  *
412
 *    overwrite, we simply choose the entry from the bucket *
413
 *    with the smallest draft and overwrite that.           *
414
 *                                                          *
415
 ************************************************************
416
 */
108 pmbaty 417
  htable = hash_table + (temp_hashkey & hash_mask);
418
  for (entry = 0; entry < 4; entry++) {
419
    if ((htable[entry].word2 ^ htable[entry].word1) == temp_hashkey) {
420
      htable[entry].word1 &= ~((uint64_t) 0x1fffff << 32);
421
      htable[entry].word1 |= (uint64_t) tree->pv[0].path[ply] << 32;
422
      htable[entry].word2 = temp_hashkey ^ htable[entry].word1;
33 pmbaty 423
      break;
424
    }
425
  }
426
  if (entry == 4) {
427
    replace = 0;
428
    replace_draft = 99999;
108 pmbaty 429
    for (entry = 0; entry < 4; entry++) {
430
      age = htable[entry].word1 >> 55;
431
      draft = (htable[entry].word1 >> 17) & 0x7fff;
33 pmbaty 432
      if (age != transposition_age && replace_draft > draft) {
108 pmbaty 433
        replace = htable + entry;
33 pmbaty 434
        replace_draft = draft;
435
      }
436
    }
437
    if (!replace) {
108 pmbaty 438
      for (entry = 0; entry < 4; entry++) {
439
        draft = (htable[entry].word1 >> 17) & 0x7fff;
33 pmbaty 440
        if (replace_draft > draft) {
108 pmbaty 441
          replace = htable + entry;
33 pmbaty 442
          replace_draft = draft;
443
        }
444
      }
445
    }
446
    replace->word1 = word1;
447
    replace->word2 = temp_hashkey ^ word1;
448
  }
449
}