crafty-22.0

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

crafty-22.0

Post by bob »

We are now doing final testing to make sure nothing has been broken. The primary change for 22.0 is code size... Here is a comparison of lines of code for 21.7 vs 22.0:

crafty% cat *.h *.c | wc -l
39405
crafty% cd /home/old/crafty-21.7
crafty% cat *.h *.c | wc -l
43332

We dropped almost 4,000 lines of C from the engine kernel. There are a few places left with duplicated black/white code, but they are in places that are not used to actually play chess (initialization, for example). These will be cleaned up soon as well, but not in 22.0, so another 500+ line reduction will likely happen.

This will match the crafty-21.7 version we used in the last CCT event, except for bugfixes. In doing this conversion, several evaluation bugs were found in 21.7 and they were fixed there and in 22.0 so I could maintain exact node counts for debugging. I particularly found problems in EvaluatePassedPawnRaces() and still have some work I want to do there probably for 22.1, because the old code just had some non-obvious bugs that were problematic to say the least...

We will probably stick this on the ftp box tomorrow or wednesday once the final 20K game validation run completes...
Guetti

Re: crafty-22.0

Post by Guetti »

I also ditched the color dependent code in move generation with one exception, to generate rochade moves. Is it possible to generate these independant form the color? I have stuff there like if (fromsq(King) == E1) etc.
Tord Romstad
Posts: 1808
Joined: Wed Mar 08, 2006 9:19 pm
Location: Oslo, Norway

Re: crafty-22.0

Post by Tord Romstad »

Guetti wrote:I also ditched the color dependent code in move generation with one exception, to generate rochade moves. Is it possible to generate these independant form the color? I have stuff there like if (fromsq(King) == E1) etc.
Yes, it is easy to make things like this color-independent:

Code: Select all

if(fromsq(King) == (E1^(color*070)))
I have found this to be useful so often that I have written a little function for it:

Code: Select all

inline Square relative_square(Color c, Square s) {
  return Square(int(s) ^ (int(c) * 070));
}
With this function, relative_square(WHITE, SQ_E1) returns SQ_E1, while relative_square(BLACK, SQ_E1) returns E8.

Replace the constant 070 by 0x70 if you use a 128-square board.

Tord
Guetti

Re: crafty-22.0

Post by Guetti »

Tord Romstad wrote:
Guetti wrote:I also ditched the color dependent code in move generation with one exception, to generate rochade moves. Is it possible to generate these independant form the color? I have stuff there like if (fromsq(King) == E1) etc.
Yes, it is easy to make things like this color-independent:

Code: Select all

if(fromsq(King) == (E1^(color*070)))
I have found this to be useful so often that I have written a little function for it:

Code: Select all

inline Square relative_square(Color c, Square s) {
  return Square(int(s) ^ (int(c) * 070));
}
With this function, relative_square(WHITE, SQ_E1) returns SQ_E1, while relative_square(BLACK, SQ_E1) returns E8.

Replace the constant 070 by 0x70 if you use a 128-square board.

Tord
Interesting.

For pawns I simply made an array advance_one[2] = {8, -8} and advance_two[2] = {16, -16} to get fromsq+advance_one[WHITE] = fromsq+8, fromsq+advance_one[BLACK] = fromsq-8.

But your square conversion didn't come to my mind.
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: crafty-22.0

Post by bob »

Guetti wrote:I also ditched the color dependent code in move generation with one exception, to generate rochade moves. Is it possible to generate these independant form the color? I have stuff there like if (fromsq(King) == E1) etc.
yes. if (fromsq(king) == Esquare[side]

where Esquare is defined:

int Esquare[2] = { E8, E1 };

That's what I did..
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: crafty-22.0

Post by bob »

Guetti wrote:
Tord Romstad wrote:
Guetti wrote:I also ditched the color dependent code in move generation with one exception, to generate rochade moves. Is it possible to generate these independant form the color? I have stuff there like if (fromsq(King) == E1) etc.
Yes, it is easy to make things like this color-independent:

Code: Select all

if(fromsq(King) == (E1^(color*070)))
I have found this to be useful so often that I have written a little function for it:

Code: Select all

inline Square relative_square(Color c, Square s) {
  return Square(int(s) ^ (int(c) * 070));
}
With this function, relative_square(WHITE, SQ_E1) returns SQ_E1, while relative_square(BLACK, SQ_E1) returns E8.

Replace the constant 070 by 0x70 if you use a 128-square board.

Tord
Interesting.

For pawns I simply made an array advance_one[2] = {8, -8} and advance_two[2] = {16, -16} to get fromsq+advance_one[WHITE] = fromsq+8, fromsq+advance_one[BLACK] = fromsq-8.

But your square conversion didn't come to my mind.
I started off with Tord's approach, but I found many cases where that didn't work (your 8, -8 is one example. I finally defined an array "map_squares[side][square]" to do this for all squares.

Now I just do map_squares[side][E1] and get E1 for side = wtm, and E8 for side = btm. You can bury that in a Macro to make it less intrusive if you want...
mathmoi
Posts: 287
Joined: Mon Mar 13, 2006 5:23 pm
Location: Québec

Re: crafty-22.0

Post by mathmoi »

bob wrote:There are a few places left with duplicated black/white code, but they are in places that are not used to actually play chess (initialization, for example).
Hi M. Hyatt,

May I ask what did motivate you to remove the black/white duplicated code from your engine? I know it is less bug prone to maintain one function instead of two almost identical ones. But I wonder if you expect to see any speed improuvments?

In my engine, that is in C++, I use templated functions, so I only have to write/maintain one fonction, but the compilator generates two. I always thought I was benefiting from the best of both world. However if having duplicated functions causes a performance penalty, I might be better to drop my templates.

For thoses interested here is an example of a templated function :

Code: Select all

template <PieceColor color, bool bCaptures, bool bRook, bool bQueen>
inline void MoveGenerator&#58;&#58;GenerateRookLike ( MoveList& liste )
&#123;
   //...
&#125;
This one function can replace theses 8 functions :

GenWhiteRookMoves
GenWhiteQueenMoves
GenWhiteRookCaptures
GenWhiteQueenCaptures
GenBlackRookMoves
GenBlackQueenMoves
GenBlackRookCaptures
GenBlackQueenCaptures
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: crafty-22.0

Post by bob »

mathmoi wrote:
bob wrote:There are a few places left with duplicated black/white code, but they are in places that are not used to actually play chess (initialization, for example).
Hi M. Hyatt,

May I ask what did motivate you to remove the black/white duplicated code from your engine? I know it is less bug prone to maintain one function instead of two almost identical ones. But I wonder if you expect to see any speed improuvments?

In my engine, that is in C++, I use templated functions, so I only have to write/maintain one fonction, but the compilator generates two. I always thought I was benefiting from the best of both world. However if having duplicated functions causes a performance penalty, I might be better to drop my templates.

For thoses interested here is an example of a templated function :

Code: Select all

template <PieceColor color, bool bCaptures, bool bRook, bool bQueen>
inline void MoveGenerator&#58;&#58;GenerateRookLike ( MoveList& liste )
&#123;
   //...
&#125;
This one function can replace theses 8 functions :

GenWhiteRookMoves
GenWhiteQueenMoves
GenWhiteRookCaptures
GenWhiteQueenCaptures
GenBlackRookMoves
GenBlackQueenMoves
GenBlackRookCaptures
GenBlackQueenCaptures
There are two issues I wanted to address:

(1) development. Duplicated code complicates testing since I have to di it twice, once for black, once for white, to make sure no asymmetry creeps in (Crafty's evaluation has been 100% symmetric for about 2 years and I want to make certain that happens. It is a far less daunting task to modify something when you know it will require less than 1/2 the effort it would take if the duplication was still there.

(2) cache footprint. Duplicated code has to reside in cache to execute, and it takes twice as much. I had originally thought that these changes would slightly slow things down since some constants and stuff are now indexed by [side] so that they work for both colors. But, in fact, the overall speed went up a bit and will probably go up more as I can now do twice as much work per unit of time since I am not doing everything twice...

A 3rd advantage is that it simply makes the code easier to read, not that removing over 4,000 lines of code was a bad thing by itself...
Tord Romstad
Posts: 1808
Joined: Wed Mar 08, 2006 9:19 pm
Location: Oslo, Norway

Re: crafty-22.0

Post by Tord Romstad »

Guetti wrote:For pawns I simply made an array advance_one[2] = {8, -8} and advance_two[2] = {16, -16} to get fromsq+advance_one[WHITE] = fromsq+8, fromsq+advance_one[BLACK] = fromsq-8.
I do the same for pawns, except that I have hidden it in an inline function:

Code: Select all

inline SquareDelta pawn_push&#40;Color c&#41; &#123;
  static const SquareDelta PawnPush&#91;2&#93; = &#123;DELTA_N, DELTA_S&#125;;
  return PawnPush&#91;c&#93;;
&#125;
It would be easy to do the same without an array, though:

Code: Select all

inline SquareDelta pawn_push&#40;Color c&#41; &#123;
  return DELTA_N + 2 * int&#40;c&#41; * DELTA_S;
&#125;
I think it is a good idea to hide things like this in little inline functions. It makes it trivial to change from one approach to another, and it prevents you from trying to index the array by something else than a color, which prevents lots of silly little bugs. I prefer not to see low-level implementation details like arrays in the high-level parts of my code.

I don't have a special function for double pawn pushes; just doing 2 * pawn_push(color) is simple enough for me.

Tord
Tord Romstad
Posts: 1808
Joined: Wed Mar 08, 2006 9:19 pm
Location: Oslo, Norway

Re: crafty-22.0

Post by Tord Romstad »

bob wrote:
Guetti wrote:
Tord Romstad wrote:
Guetti wrote:I also ditched the color dependent code in move generation with one exception, to generate rochade moves. Is it possible to generate these independant form the color? I have stuff there like if (fromsq(King) == E1) etc.
Yes, it is easy to make things like this color-independent:

Code: Select all

if&#40;fromsq&#40;King&#41; == &#40;E1^&#40;color*070&#41;))
I have found this to be useful so often that I have written a little function for it:

Code: Select all

inline Square relative_square&#40;Color c, Square s&#41; &#123;
  return Square&#40;int&#40;s&#41; ^ &#40;int&#40;c&#41; * 070&#41;);
&#125;
With this function, relative_square(WHITE, SQ_E1) returns SQ_E1, while relative_square(BLACK, SQ_E1) returns E8.

Replace the constant 070 by 0x70 if you use a 128-square board.

Tord
Interesting.

For pawns I simply made an array advance_one[2] = {8, -8} and advance_two[2] = {16, -16} to get fromsq+advance_one[WHITE] = fromsq+8, fromsq+advance_one[BLACK] = fromsq-8.

But your square conversion didn't come to my mind.
I started off with Tord's approach, but I found many cases where that didn't work (your 8, -8 is one example.
Not, that's not an example, because they are two entirely different operations. Flipping a square vertically is obviously not the same as flipping a square delta.

By the way, doing pawn pushes for both colors without an array is also easy, as explained in one of my other posts. In any case, because the array will be so tiny, it doesn't matter at all which approach you use.
I finally defined an array "map_squares[side][square]" to do this for all squares.

Now I just do map_squares[side][E1] and get E1 for side = wtm, and E8 for side = btm.
My approach of doing square ^ (side * 070) does exactly the same for all squares, as you can easily prove.
You can bury that in a Macro to make it less intrusive if you want...
I agree, of course this should be done in a macro or inline function. My own preference would be to use an inline function, in order to avoid the pitfalls of preprocessor macros and to ensure some type safety. I honestly don't understand why people are still using preprocessor macros, but that's probably just because of my general ignorance about C/C++ programming.

Tord