Move ordering help

Discussion of chess software programming and technical issues.

Moderator: Ras

Richard Allbert
Posts: 795
Joined: Wed Jul 19, 2006 9:58 am

Move ordering help

Post by Richard Allbert »

Hi,

During last night's Online blitz I became a little frustrated with the depth Jabba was achieving - somtimes, on a 2.4ghz Processor with 500kns, it failed to get past depth 5 in 10s. Mostly playing at depth 6. The game it won was pure luck vs Micromax - MM was winning hands down until its skeleton eval let it down.

So I've spent the past 6 hours checking through lots of data, and can't find what is wrong - I was assuming move ordering is to blame.

I've disabled Hash table, null move, PVS so it's a pure alpha beta searcher with nothing else.

Here's the bottom of the move loop, with the stats being collected:

Code: Select all

    gen_all_moves(game, mat, mlist, hashmove);
    uint *list = mlist.p2list(game.getply());
    int val = -matescore;
    uint played = 0;
    uint bestmove = NULLMOVE;
    uint from;
    int old_alpha = alpha;

    if(check && game.getply()==0) { stats->checkext++; depth++; }

    for(uint i = 0; i < mlist.getcount(game.getply()); ++i)
    {
        picknext(i,mlist);

     if (makemove(game,mat,his,list[i]))
     {
       takemove(game,mat,his);
       continue;
     }

     check = incheck(game,mat,game.getside());
     ext = 0;
     if(check) { stats->checkext++; ext = 1; }
  
       val = -alphabeta( -beta, -alpha, depth-1+ext, true, check);
  
     takemove(game,mat,his);
     if(status->stopped == STOPPED) { return 0;}

     played++;

     from = FROM(list[i]);

     if (val > alpha)
     {
        bestmove = list[i];
        if (val >= beta)
        {
            if(played==1) stats->fhf++;
            else stats->fh++;

            mlist.score_killer(bestmove, val, game.getply());
            mlist.scoremove(list[i], depth, game.getpiece(from));
            return beta;
        }
            alpha = val;
            pvupdate(list[i]);
     }
   }

   if (played == 0)
	{
		if (check)
		{
		    return -matescore + game.getply();
		}
		else
		{
			return 0;
		}
	}

    return alpha;
Then for my sanity, the stats print for fh and fhf

Code: Select all

logger.file<<"\nfh "<<stats->fh;
    logger.file<<" fhf "<<stats->fhf<<" ordering ";
    p = percent(stats->fhf, stats->fhf+stats->fh);
    logger.file<<" "<<p<<"%";
    
where percent() is

Code: Select all

double percent(int tester, int all)
{
    double t = double(tester);
    double a = double(all);

    if(t==0 || all==0) return 0;
    else return (t/a)*100;
}
So, when I leave Jabba for nearly one minute on the position

[d]

I get...

Code: Select all

32.184-->1:position fen r2qkb1r/pp1n1ppp/2p2n2/3pp2b/4P3/3P1NPP/PPPN1PB1/R1BQ1RK1 b kq e3 0 8
32.184-->1:go infinite
32.184<--1:info depth 1 score cp -4  time 0 nodes 253 pv  d7c5
32.199<--1:info depth 2 score cp 6  time 0 nodes 827 pv  d7c5 e4d5
32.214<--1:info depth 3 score cp 4  time 31 nodes 9087 pv  f8d6 e4d5 f6d5
32.246<--1:info depth 4 score cp -6  time 62 nodes 26161 pv  f8d6 e4d5 f6d5 d2e4
32.401<--1:info depth 5 score cp 1  time 218 nodes 106314 pv  f8d6 g3g4 h5g6 g4g5 f6h5
34.148<--1:info depth 6 score cp 3  time 1965 nodes 861491 pv  f8d6 e4d5 c6d5 g3g4 h5g6 f1e1
45.132<--1:info depth 7 score cp 6  time 12948 nodes 5451271 pv  f8d6 e4d5 c6d5 g3g4 h5g6 g4g5 f6h5
85.488-->1:stop
85.535<--1:nulltry 0 nullcuts 0 0%
85.535<--1:hashcuts 0
85.550<--1:fh 63022 fhf 1785053 ordering  96.5899%
85.550<--1:nodes 2685071 qnodes 21505905  88.9005%
85.550<--1:qcuts 0
85.567<--1:checkext = 507211 pawnseventh = 106305 intopawnend = 0
85.567<--1:pvssearch = 0 pvsresearch = 0 0%
85.567<--1:bestmove f8d6
With 96% fail high first?? No way. A similar story for other middlegame positions - I use Dann's positional.epd for this kind of testing.

I'm afraid this can't be... I've been butchering various OS engines this morning to make them pure AB searchers, no pruning, no null, no hash, no extension apart from check and comaparing the stats.

Most get around 90% fail high first, and similar % of Q nodes, but reach deeper depths far easier than Jabba.

For example, Fruit 1.0 , Diablo 0.4 end up typically 2 ply deeper after 60s with Ordering 90-92% and Q nodes around 90%. Jabba sits way behind, with a theoretically much better ordering.

Which means there is a bug somewhere, but I'm getting very frustrated in wondering where to look.

Any ideas?

Thanks

Richard

PS It's tough at the bottom ;)
Mincho Georgiev
Posts: 454
Joined: Sat Apr 04, 2009 6:44 pm
Location: Bulgaria

Re: Move ordering help

Post by Mincho Georgiev »

Hello, Richard!
Maybe I'm not the right person to give you some guidelines, because what I am doing is a bit different from a lot of chess programmers, but you can try it. I see that you are updating the pv if the result is in the bounds, that doesn't work for me. Here is what I'm doing, and I am not going to say that is proper, but it works fine in my engines. I am using something like 3-way heuristics for dynamic ordering: if the result is above alpha - updating the pv. if the result is beta or outside the window - updating killers, and if the result is within the bounds - updating history counters (butterfly). I know a lot of people would disagree with my method, but it certainly gives me good results. However, the reason for your problems might not be the move ordering and i admit that i didn't examine your code snippet too closely, but
maybe this would help.

Best Regards!
Mincho.
User avatar
Roman Hartmann
Posts: 295
Joined: Wed Mar 08, 2006 8:29 pm

Re: Move ordering help

Post by Roman Hartmann »

Hi Richard,
here are a few stats from my engine. I also stripped it down to a bare alpha beta searcher. No nullmove, no hashing, no LMR. It looks like my move ordering is quite bad. But even though my move ordering is obviosly bad, it still seems to search about as deep as Jabba (AMD Sempron 1.66GHz). I ran the position about a minute +/- 1s.

Seems like my move ordering could need some improvement.

Roman

Code: Select all

roce: reset_stat

roce: setboard r2qkb1r/pp1n1ppp/2p2n2/3pp2b/4P3/3P1NPP/PPPN1PB1/R1BQ1RK1 b kq e3
 0 8

roce: analyze

  D   Time    Score  Best line
  1   0.00   -0.05   ...  bf8-d6
  2   0.01   -0.08   ...  nd7-c5 g3-g4
  3   0.03    0.03   ...  bf8-d6 e4xd5 c6xd5
  4   0.06   -0.03   ...  bf8-d6 e4xd5 c6xd5 c2-c4
  5   0.21   -0.09   ...  bf8-d6 g3-g4 bh5-g6 g4-g5 nf6-h5
  6   1.10   -0.03   ...  d5xe4 Nd2xe4 bf8-e7 Ne4xf6+ be7xf6 g3-g4 bh5-g6
  7   5.25    0.01   ...  d5xe4 Nd2xe4 nf6xe4 d3xe4 bf8-c5 g3-g4 bh5-g6
  8  39.34    0.31   ...  d5xe4 Nd2xe4 bh5xf3 Ne4xf6+ qd8xf6 Qd1xf3 o--o
                     Bc1-e3 bf8-c5
stop  0.00    0.31   d5e4 [7/34]

roce: stat

fail high on first move: 3102912 (47.69 %)
fail high not on first move: 3403497
check count: 739949
searched nodes: 24599060
QS nodes: 22258322 (47.50 %)

roce: reset_stat

roce: setboard r1bq1rk1/pppn1pb1/3p1npp/4p3/3PP2B/2P2N2/PP1N1PPP/R2QKB1R w KQ -
0 1

roce: analyze

  D   Time    Score  Best line
  1   0.00   -0.05   Bf1-c4
  2   0.02   -0.08   Nd2-c4 g6-g5
  3   0.05    0.03   Bf1-c4 e5xd4 c3xd4
  4   0.09   -0.03   Bf1-c4 e5xd4 c3xd4 c7-c5
  5   0.25   -0.09   Bf1-c4 g6-g5 Bh4-g3 g5-g4 Nf3-h4
  6   1.39   -0.03   d4xe5 nd7xe5 Bf1-e2 ne5xf3+ Be2xf3 g6-g5 Bh4-g3
  7   5.45    0.01   d4xe5 nd7xe5 Nf3xe5 d6xe5 Bf1-c4 g6-g5 Bh4-g3
  8  41.82    0.31   d4xe5 nd7xe5 Bh4xf6 ne5xf3+ Qd1xf3 qd8xf6 O--O
                     bc8-e6 Bf1-c4
stop  0.00    0.31   d4e5 [5/34]

roce: stat

fail high on first move: 2917165 (47.68 %)
fail high not on first move: 3200672
check count: 704453
searched nodes: 22338850
QS nodes: 22060863 (49.69 %)
Richard Allbert
Posts: 795
Joined: Wed Jul 19, 2006 9:58 am

Re: Move ordering help

Post by Richard Allbert »

Hi Roman,

Thanks for the reply.

How are you describing "fail high on first move" ? Do you update a counter for every position searched, or only in the case of a fail high?

e.g

A. % fail_high_first = num_fail_high_first / total_fail_highs
or
B. % fail_high_first = num_fail_high_first / total_moves_searched

I only ask because 47% is about what I get with no ordering at all... :shock:

Which is the why I asked the orignial Question - I'm not sure my stats are collected correctly.

Also, your Q nodes are a lot lower - are you pruning bad captures?

Regards

Richard
Richard Allbert
Posts: 795
Joined: Wed Jul 19, 2006 9:58 am

Re: Move ordering help

Post by Richard Allbert »

I update the pv when the result is above alpha, same as you!

And killers / History table when above beta..

It could be that my code is a bit hard to read!

Richard
User avatar
Roman Hartmann
Posts: 295
Joined: Wed Mar 08, 2006 8:29 pm

Re: Move ordering help

Post by Roman Hartmann »

Richard Allbert wrote:Hi Roman,

Thanks for the reply.

How are you describing "fail high on first move" ? Do you update a counter for every position searched, or only in the case of a fail high?

e.g

A. % fail_high_first = num_fail_high_first / total_fail_highs
or
Yes, it's case A.
B. % fail_high_first = num_fail_high_first / total_moves_searched

I only ask because 47% is about what I get with no ordering at all... :shock:
Move ordering has definitely a positive effect. If I remove move ordering, I doubt it would even reach 6 ply in one minute.
Which is the why I asked the orignial Question - I'm not sure my stats are collected correctly.

Also, your Q nodes are a lot lower - are you pruning bad captures?
Yes, if current eval+value of Queen can't make it to alpha I just return alpha. I don't do this in endgames though. Also I have a counter that should prevent getting stuck in the QS in case of a search explosion (shouldn't have any effect in the test position here).

Roman
Mincho Georgiev
Posts: 454
Joined: Sat Apr 04, 2009 6:44 pm
Location: Bulgaria

Re: Move ordering help

Post by Mincho Georgiev »

Richard Allbert wrote:I update the pv when the result is above alpha, same as you!

And killers / History table when above beta..

It could be that my code is a bit hard to read!

Richard
You are updating pv if not exceeds beta, i.e. if cutoff not occured. It's not the same at all (depending on what exactly you are doing with your pv - if it's only for printing, it's ok that way). Besides, i meant that I am updating history, just like you do with your pv. Killers are same. Anyway, it was just a sharing an idea.
Last edited by Mincho Georgiev on Sun Feb 14, 2010 4:54 pm, edited 1 time in total.
Richard Allbert
Posts: 795
Joined: Wed Jul 19, 2006 9:58 am

Re: Move ordering help

Post by Richard Allbert »

Ah, ok sorry, I misread your post. I certainly didn't mean offence.

But the problem is, I can't figure out a way of testing why the ordering stats look good and the result is bad.. :?

Thanks again

Richard
User avatar
Roman Hartmann
Posts: 295
Joined: Wed Mar 08, 2006 8:29 pm

Re: Move ordering help

Post by Roman Hartmann »

Richard Allbert wrote:Ah, ok sorry, I misread your post. I certainly didn't mean offence.

But the problem is, I can't figure out a way of testing why the ordering stats look good and the result is bad.. :?

Thanks again

Richard
Maybe you should check how bad those few cases are when you don't have a fail high on the first move. And maybe it would also be a better idea to compare the total nodes with the fail high nodes.

Roman
User avatar
hgm
Posts: 28454
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Move ordering help

Post by hgm »

I always take statistics split out per remaining search depth. That makes it easier to spot where problems might ly. E.g.

Code: Select all

# hash-table address = 5f0020
tellics say     HaQiKi D 0.9
tellics say     by H.G. Muller
new
level 40 5 0
post
time 500000
go
# eval=0
 1     44      0         62       19       19 f0e1 {3,0(0,0,45,45)9696}
 2     12      0        536       39       38 f0e1 {3,0(0,0,45,45)9696}
 3     28      0       1922       52       49 f0e1 {3,0(0,0,45,45)9696}
 4     12      1       9009       66       60 f0e1 {3,0(0,0,45,45)9696}
 5     28      3      13193       76       66 f0e1 {3,0(0,0,45,45)9696}
 6     12      8      45904       87      107 f0e1 {3,0(0,0,45,45)9696}
 7     24     15      88736      107      139 f0e1 {3,0(0,0,45,45)9696}
 8     20     35     226765      120      324 f0e1 {3,0(0,0,45,45)9696}
 9     20     90     586806      150      526 f0e1 {3,0(0,0,45,45)9696}
10     24    151    1057835      174      787 f0e1 {3,0(0,0,45,45)9696}
11     28    378    2660264      247     2200 f0e1 {3,0(0,0,45,45)9696}
12     20    919    6541777      461     6312 f0e1 {3,0(0,0,45,45)9696}
13     24   1992   13682423      609    12489 f0e1 {3,0(0,0,45,45)9696}
14     20   4829   33977443      799    35822 f0e1 {3,0(0,0,45,45)9696}
15     16  13548   95311948     1880   133844 f0e1 {3,0(0,0,45,45)9696}
# stop iterating, time=135484, tl=58139, id=15 md=63
move f0e1
 0. 74531114 36608995  6630521    22796      113      125 6281880 317541  27886
 1. 13699750 10525529  4079868    19833   550381   106115 3279072 470310 182704
 2.  3925761  2860819   562710   261750    80183    29276 446367  58519  26093
 3.  1544730  1145644   244663   162823    44909    15563 204748  19790   9201
 4.   963072   760754    73603    48215    12732     5147  60642   6624   2958
 5.   377936   271389    51351    31970    16009     5601  38664   8018   3064
 6.   169040   129951    19798    16376     4493     1745  17656    977    557
 7.    55649    39713    11199     9382     3427     1335   9942    708    326
 8.    27192    21116     4090     3636     1307      439   3782    126    113
 9.    11135     8377     1856     1709      718      223   1739     59     31
10.     3918     2853      774      712      311       88    734     18     13
11.     2006     1449      474      454      248       47    459      5      4
The lower lines are the stats for each depth. What you see after the remaining depth, from left to right, is:

total nodes
null-move and stand-pat
other cutoffs
hash-move
killer1
killer2
first move
second move
third move

E.g. in QS (d=0), you can see that about half the nodes are decided by stand pat, and only about 9% require a move for cutoff. Of these, 94.7% ar cut by the first move. (Almost never a hash move, while killers are probably accidental matches of captures with non-capture killers). This is a much higher first-move cut rate than at d=1, where I acheive only ~80% first-move cuts. But because the total is dominated so much by the QS nodes, that would not be apparent from the total at all.