Looking for TT policy advice

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
Guenther
Posts: 4605
Joined: Wed Oct 01, 2008 6:33 am
Location: Regensburg, Germany
Full name: Guenther Simon

Re: Looking for TT policy advice

Post by Guenther »

RubiChess wrote: Sun Oct 06, 2019 12:27 pm
xr_a_y wrote: Sun Oct 06, 2019 8:41 am
Here is some input, at TC 40/20sec TT=128Mb

Code: Select all

Rank Name                          Elo     +/-   Games   Score   Draws
   1 komodo-13-64-ja               108      23     692   65.1%   28.2%
...
...
Is this really Komodo 13? Only 11 Elo better than Rodent?
Andreas, I am quite sure (JA compile!) it is Komodo 1.3.
https://rwbc-chess.de

trollwatch:
Chessqueen + chessica + AlexChess + Eduard + Sylwy
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: Looking for TT policy advice

Post by xr_a_y »

yes 1.3 of course :)

Minic is totally killed by Komodo 10, I don't even try 13 yet ...
RubiChess
Posts: 584
Joined: Fri Mar 30, 2018 7:20 am
Full name: Andreas Matthies

Re: Looking for TT policy advice

Post by RubiChess »

xr_a_y wrote: Sun Oct 06, 2019 1:07 pm yes 1.3 of course :)

Minic is totally killed by Komodo 10, I don't even try 13 yet ...
Ah, okay. That makes sense. Didn't know there was a 1.3. Just found it even for download on their site.
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: Looking for TT policy advice

Post by xr_a_y »

hgm wrote: Fri Oct 04, 2019 3:18 pm At short TC the entire tree often fits in the TT, so the replacement algorithm does not matter as it is hardly exercised. My exprience is that you only start to notice a slowdown if you shrink the TT size to less than 10% of the node count, even with rather simple replacement schemes.

For running with very much overloaded TT, it is important to make sure intermediate depth are not completely pushed out of the table: what is not deep enough to fight its way into a depth-preferred slot, will end up in the always-replace slot. There the survival time is very short, as these slots are constantly bombarded by a flood of d=0 (or whetever your lowest stored depth is) entries.

One way around that is allowing shallow entries to replace deeper ones when the depth of the latter is overrepresented in the table. (E.g. when there are more of that depth than d=1 entries.) For this you would have too keep a histogram of table depth, however. An alterative is to use under-cut replacement, where you replace a depth-preferred entry by a new entry of a depth that is equal, higher or exactly one lower. This will eventually flush any depth out of the table, so one should view it as a kind of always-replace slot. But it tends to hang on longer to the deeper entries.

My current favorite scheme is to probe only 3 entries in each bucket; the hash key determining which 3: one that keeps track of the highest depth so far (subject to aging uring game play, but not in analysis), one under-cut slot, and one always-replace slot. If the total number of slots in a bucket is not a multiple of 3, some keys can share some of the entries (e.g. only a single always-replace entry per bucket).
After working on the little bug I found previously, I tried some different TT scheme without any success. I use the same table to store eval and score and it is used in qsearch also. It feels like something about depth is needed, about entry age maybe but I couldn't found something better than just replacing the worst depth (with only 2 buckets ...)
User avatar
hgm
Posts: 27789
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Looking for TT policy advice

Post by hgm »

I suppose you mean "with only two slots per bucket".

True, you don't have much leeway in that case, as it is very important to always store in the TT. You could use undercut replacement, replacing the highest depth of the pair rather than the lowest when the new depth is exactly one less. Or do that in only 25% (say) of the cases, based on the low bits of the node counter.

But it would be better to have more slots per bucket. I usuallytry to organize the TT such that buckets are exactly 64 bytes (so they fit in a cache line, and a probe will need only a single memory (burst) access). That often gives me 5 or 6 slots per bucket. Then I use a probe scheme that probes 3 of thos slots. Like 0, 1 and 2 or 0, 3 and 4 when I have 5 slots (depending on the hash key), where 0 would be the always-replace slot.
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: Looking for TT policy advice

Post by xr_a_y »

hgm wrote: Fri Oct 11, 2019 9:07 am I suppose you mean "with only two slots per bucket".

True, you don't have much leeway in that case, as it is very important to always store in the TT. You could use undercut replacement, replacing the highest depth of the pair rather than the lowest when the new depth is exactly one less. Or do that in only 25% (say) of the cases, based on the low bits of the node counter.

But it would be better to have more slots per bucket. I usuallytry to organize the TT such that buckets are exactly 64 bytes (so they fit in a cache line, and a probe will need only a single memory (burst) access). That often gives me 5 or 6 slots per bucket. Then I use a probe scheme that probes 3 of thos slots. Like 0, 1 and 2 or 0, 3 and 4 when I have 5 slots (depending on the hash key), where 0 would be the always-replace slot.
Well for now I use quite big TT entry : 24bytes ...

Code: Select all

struct Entry{
    Move m; //int
    Hash h; // int64
    ScoreType score, eval; //short int
    Bound b; // char
    DepthType d; // signed char
    short int generation;
};
I guess only a little part (short int ?) of the hash can be stored to verify ... just xoring hash versus move for example...
User avatar
pedrox
Posts: 1056
Joined: Fri Mar 10, 2006 6:07 am
Location: Basque Country (Spain)

Re: Looking for TT policy advice

Post by pedrox »

xr_a_y wrote: Fri Oct 11, 2019 4:54 pm

Code: Select all

struct Entry{
    Move m; //int
    Hash h; // int64
    ScoreType score, eval; //short int
    Bound b; // char
    DepthType d; // signed char
    short int generation;
};
This is not 20 bytes?

4 + 8 + 2 + 2 + 1 + 1 + 2 = 20
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: Looking for TT policy advice

Post by xr_a_y »

Yes but as the struct is ill-organized there is some padding in the middle. That can be optimized in first place.
User avatar
hgm
Posts: 27789
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Looking for TT policy advice

Post by hgm »

Sure, a large part of the hash key is completely redundant, as it can only be the index of the entry. Usually storing 32 bits as signature is good enough, and I was told Stockfish even uses only 16 bits. Wasting memory on padding is of course never a good idea. Only or very large or complex variants I need more than 2 bytes or the move.

It is also not clear to me what 'generation' is used for.
User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Re: Looking for TT policy advice

Post by xr_a_y »

hgm wrote: Fri Oct 11, 2019 7:20 pm Sure, a large part of the hash key is completely redundant, as it can only be the index of the entry. Usually storing 32 bits as signature is good enough, and I was told Stockfish even uses only 16 bits. Wasting memory on padding is of course never a good idea. Only or very large or complex variants I need more than 2 bytes or the move.

It is also not clear to me what 'generation' is used for.
"generation" is "age" of the entry (it could be smaller or even a bool the way it is used for now...).

a move is coded so that

Code: Select all

inline ScoreType Move2Score(Move h) { assert(h != INVALIDMOVE); return (h >> 16) & 0xFFFF; }
inline Square    Move2From (Move h) { assert(h != INVALIDMOVE); return (h >> 10) & 0x3F  ; }
inline Square    Move2To   (Move h) { assert(h != INVALIDMOVE); return (h >>  4) & 0x3F  ; }
inline MType     Move2Type (Move h) { assert(h != INVALIDMOVE); return MType(h & 0xF)    ; }