Moderator: Andres Valverde
Sune Fischer wrote:I think you have the fastest way for rays. It's not easy to beat a table lookup using on-the-fly computations.
I don't see what's so ugly about using precomputed lookup tables though
I thought about adding a piece list, like you say it's faster to "get them out", but I decided against it for several reasons. For one thing it's more code, more maintenance and therefore more errorprone and "ugly", IMO.
Secondly, how do you work on a piece list? I want to know the number of rooks on openfiles, the bishops in the long diagonal the pawns in front of the king etc..
Almost everytime you need something you need it in reverse!
To find this information without bitboards you have to scan through the list again and again which is very much uglier and slower than extracting a bit from a bitboard, IMO.
In some cases you don't have to extract any bits because a simple & will tell you there are no pieces there, and in other cases just knowing if there is a piece or not will be sufficient.
In fact, updating the piece list when moves are made and unmade is less work (in terms of lines of code as well as execution time) than updating bitboards. But having bitboards *and* piece lists seems very redundant. Essentially you end up maintaining exactly the same information in two different data structures, which doesn't look like the right thing to do.
I'm not sure I understand what you mean here. I always loop through all pieces on the board at least once in the evaluation function, and I don't see how I can avoid this even when using bitboards. During this loop, the kind of information you describe is easily obtained.
In some cases you don't have to extract any bits because a simple & will tell you there are no pieces there, and in other cases just knowing if there is a piece or not will be sufficient.
This is, as far as I can see, the main advantage with bitboards compared to piece lists in this respect. I hoped that this would be sufficient to compensate for the expensive operation of looping through nonzero bits. My experience so far is that it isn't even close to sufficient.
Tord Romstad wrote:What is the best way to compute a bitboard containing 1s at all squares between two given squares, and 0s everywhere else? For instance, given the squares a2 and f7, I want a bitboard with 1s at b3, c4, d5 and e6. I currently do this with a huge lookup table indexed by all pairs of squares, which is of course a very ugly and unsatisfactory solution.
bitboard getray(int sq1, int sq2)
{
if (sq1 > sq2)
return raytable[sq1][sqdelta[sq1 - sq2]];
else
return raytable[sq2][sqdelta[sq2 - sq1]];
}
bitboard getray(int sq1, int sq2)
{
if (sq1 > sq2)
return (sq1 - sq2) & rays[sq1][direction[sq1 - sq2]];
else
return (sq2 - sq1) & rays[sq2][direction[sq2 - sq1]];
}
It seems that one of the main reasons for the slowdown in the bitboard version of Glaurung is that looping through the nonzero bits of a bitboard is very slow compared to looping through a piece list. When I introduced bitboards, I ripped out the piece lists in order to simplify and speed up the make_move and unmake_move functions. I am now wondering whether this was really a good idea. Do bitboard engines usually have some kind of piece lists in addition to bitboards?
// I think this is right... (untested)
unsigned bb_to_x88 (unsigned sq)
{
return (sq & 56) + sq;
}
// Again, untested...
table[sq1 - sq2 + 128] << min(sq1, sq2);
// Example:
// table[g7 - b2 + 128] contains this bitboard:
// 00000000
// 00000000
// 00000000
// 00001000
// 00010000
// 00100000
// 01000000
// 00000000
// min(g7,b2) == b2, so shift by the value of b2 (0...63 format)
I also need to nitpick Sune . You don't have to use lookup tables to update rotated bitboards. You can rotate the square using some math and then get the bitmask of the rotated square. For instance, to rotate a square 90 degrees you can convert the square to its rank and file, then swap them and recompute the square. I don't know if it would be faster though
Sune Fischer wrote:Hi Russell,
I think the 256 table needs a special cases for File(sq1)<File(sq2), ie. what if the squares are d2 and a5 then the ray needs to point in the other way.
With some branches and shifts it's no doubt possible to compress the table.
I guess I don't have to say this is both ugly, complicated and positively slower.
table[d2-a5+128] contains:
00000000
00000000
00000000
00000000
00000000
00000100
00000010
00000001
Then left shift that by min(d2, a5).
Russell Reagan wrote:
- Code: Select all
table[d2-a5+128] contains:
00000000
00000000
00000000
00000000
00000000
00000100
00000010
00000001
Then left shift that by min(d2, a5).
What do you think?
bitboard_t in_between (int sq1, int sq2)
{
return table[bb_to_x88(sq1) - bb_to_x88(sq2) + 128] << min(sq1, sq2);
}
print_bitboard( in_between(d2, a5) );
print_bitboard( in_between(a5, d2) );
--------
--------
--------
--------
-#------
--#-----
--------
--------
--------
--------
--------
--------
-#------
--#-----
--------
--------
Sune Fischer wrote:
Look at Crafty, it's got 2 MB tables and is still crunching happily along at 1 MNps, it doesn't even seem to profit from larger caches!
So, you want to see ragingly high nps you have to drop all the board scans and start using some tables!
There, I said it.
Okay, alternatives.
Well what about a switch in the x-y direction to generate that attack line from y. Then see if it hits x and z. Or start by checking that x,y and y,z are on a line.
You can convert to 0x88 first so you only need that tiny 256 table
In fact, updating the piece list when moves are made and unmade is less work (in terms of lines of code as well as execution time) than updating bitboards. But having bitboards *and* piece lists seems very redundant. Essentially you end up maintaining exactly the same information in two different data structures, which doesn't look like the right thing to do.
Huh less work?
Bitboards only needs an xor to lift up and an xor to set back down.
Ok the rotated needs tables..
Russell Reagan wrote:Hi Tord,
Would you be willing to give an example of how you will use the "ray between squares" bitboard information?
Sometimes when a person is used to offset based tricks, bitboard tricks are not always obvious.
Maybe there is a better approach than the "ray between squares" bitboard idea.
Anyway, you can use the 0x88 trick that you are no doubt familiar with (unique square differences, 256-element lookup table). Converting the bitboard coordinates (0...63) to 0x88 coordinates (0...128) is simple.
Tord Romstad wrote:Welcome on board!
Definitely. The data structures we have experience with shape our way of thinking to a great extent. I found it amusing to read one of Sune's replies in this thread, where he wrote (about non-bitboard board representations) that "almost every time you need something you need it in reverse". I could have said exactly the same thing about bitboards.
It is, but it is even simpler to just use 0..128 as bitboard coordinates, without doing any conversion. This is what I do. My first_1() function returns a square index in the 0..128 range.
// Originally by Eugene Nalimov
int Bitscan (Bitboard arg) {
int result = 0;
if (arg > 0xFFFFFFFF) {
arg >>= 32;
result = 32;
}
if (arg > 0xFFFF) {
arg >>= 16;
result += 16;
}
if (arg > 0xFF) {
arg >>= 8;
result += 8;
}
return result + table8[arg];
}
Thanks a lot for your code! I haven't tried the idea yet (no time for programming these days), but it looks like the most interesting I have seen so far.
Tord Romstad wrote:Hi all,
It seems that one of the main reasons for the slowdown in the bitboard version of Glaurung is that looping through the nonzero bits of a bitboard is very slow compared to looping through a piece list. When I introduced bitboards, I ripped out the piece lists in order to simplify and speed up the make_move and unmake_move functions. I am now wondering whether this was really a good idea. Do bitboard engines usually have some kind of piece lists in addition to bitboards?
Tord
Return to Programming and Technical Discussions
Users browsing this forum: No registered users and 33 guests