Chessnut1071 wrote: ↑Fri Dec 24, 2021 3:49 am
I have a very simple bitboard, a precomputed move vector (8) for each of the 64 squares. I just use the intrinsics TrailingZeros and LeadigZeros after I XOR out the last move to find the next move. I stop at a blocker.
btw, that 13.8 million was in debug mode. Also, I capture the following data for each move:
1 check 2 double check 3 discovered check 4 pin 5 ent passant 6 pawn promotion 7 move direction 8 capture 9 defender.
It's difficult for me to understand your move generation logic without seeing your code. In previous discussions- if I recall correctly- I and other developers have asked how your code accounts for every permutation of occupancy along a move direction ray. I don't recall seeing that code. Though "I stop at a blocker" suggests your move generator depends on a loop and does some bookkeeping to track blockers in each direction. Sharing your code would clarify this.
10,000,000 engine call x AVG 59 pseudo moves = 590,000,000 (nodes) / 21.57 seconds ( release mode ) = 27.352,804 nodes per second
Actually, from what I'm hearing, my engine is super SLOW! Are you judging from legal moves or pseudo moves?
"Engine calls" is not a phrase anyone on this forum is familiar with. It does not have a clear, unambiguous definition chess engine programmers agree upon. Also unclear is the significance of 59 average pseudo-legal moves. How did you measure that number? Just because the root position has 59 pseudo-legal moves does not mean, on average, every position in the search tree has 59 pseudo-legal moves. Likely less.
The standard metric used by TalkChess forum members is Nodes Per Second (NPS), measured by incrementing the node count whenever Board.PlayMove(move) is called. Correct me if I'm wrong (forum members), but I believe that's the metric most engine developers use. It measures the rate at which an engine examines legally reachable (well, other than via null move) positions.
Also, it's helpful to state whether you've measured NPS during perft (pure move generation) or during search (usually alpha / beta minimax + depth reductions + pruning).
I don't know if there's a standard, but if there is would really like to know. You mentioned that nodes are counted whenever the make move function is called. I presume it excludes null moves for instance. Are there any other exceptions? How about illegal moves? My engine generates pseudo legal and to know if legal, i try the move. Is there a link to a talkchess thread or cpw page that discusses this topic?
In my case, i count nodes as every call to the recursive AB function and Qsearch functions, excluding leaf nodes, repetitions, 50 rule and TT hits.
Normally the reported nps refers to number of calls to the evaluaton, or to the move generator. In engines leaf nodes typically call both (the move generator to ascertain there are no captures to search). In engines one typically refrains from frivolous calling of MakeMove, but only calls it if the aim i to actually do something in that node. Then calling MakeMoves amounts to the same thing. It is very easy in that case to drive up the nps enormously, by making lots of moves to nodes where nothing is done: you can do nothing an extremely large number of times per second. Although the MakeMove is extremely cheap compared to move generation, it is not absolutely free, so such pointless making of moves still slows down the engine.
federico wrote: ↑Sun Dec 26, 2021 6:34 pm
In my case, i count nodes as every call to the recursive AB function and Qsearch functions, excluding leaf nodes, repetitions, 50 rule and TT hits.
federico wrote: ↑Sun Dec 26, 2021 6:34 pm
I don't know if there's a standard, but if there is would really like to know. You mentioned that nodes are counted whenever the make move function is called. I presume it excludes null moves for instance. Are there any other exceptions? How about illegal moves? My engine generates pseudo legal and to know if legal, i try the move. Is there a link to a talkchess thread or cpw page that discusses this topic?
In my case, i count nodes as every call to the recursive AB function and Qsearch functions, excluding leaf nodes, repetitions, 50 rule and TT hits.
I'm glad you asked. I discovered I over-count by incrementing node count during move legality testing. I'll remove that. I do increment node count when playing a null move. I advance the position counter and update a few position properties, so I consider that a node.
I don't count nodes during move generation, piece mobility evaluation, or any other evaluation. So once I correct the move legality over-counting, that reduces NPS in my engine to 1) played legal moves and 2) played null moves.
Compared to strict measurements of Elo, NPS is a nebulous concept.
federico wrote: ↑Sun Dec 26, 2021 6:34 pm
I don't know if there's a standard, but if there is would really like to know. You mentioned that nodes are counted whenever the make move function is called. I presume it excludes null moves for instance. Are there any other exceptions? How about illegal moves? My engine generates pseudo legal and to know if legal, i try the move. Is there a link to a talkchess thread or cpw page that discusses this topic?
In my case, i count nodes as every call to the recursive AB function and Qsearch functions, excluding leaf nodes, repetitions, 50 rule and TT hits.
I'm glad you asked. I discovered I over-count by incrementing node count during move legality testing. I'll remove that. I do increment node count when playing a null move. I advance the position counter and update a few position properties, so I consider that a node.
I don't count nodes during move generation, piece mobility evaluation, or any other evaluation. So once I correct the move legality over-counting, that reduces NPS in my engine to 1) played legal moves and 2) played null moves.
Compared to strict measurements of Elo, NPS is a nebulous concept.
? on your engine. When you develop the list of pseudo moves how much information do you take along with the piece and move. Many report that they take a few as possible since many pseudo moves will be thrown away. In addition to piece and move, I take capture/defender, move direction, check, double check, discovered check, check direction, attacker, attacker location and ent passant. The time overhead is only 25% for that information, and if you take it later the time savings disappears. My objective is checkmate, not ELO, so I may need more info for my objective. I'd be interested to see how much and when you take all the other associated data with a given move.
Chessnut1071 wrote: ↑Mon Dec 27, 2021 7:38 pmWhen you develop the list of pseudo moves how much information do you take along with the piece and move... In addition to piece and move, I take capture/defender, move direction, check, double check, discovered check, check direction, attacker, attacker location and ent passant.
I collect similar information minus some check details. During pseudo-legal move generation, MadChess collects:
From Square
To Square
Victim (or Piece.None)
Attacker (the moving piece: Piece.Pawn, Piece.Knight, etc)
Piece-specific properties (En Passant, Pawn Move, Double Pawn Move, Promoted Piece, King Move, Castling)
During staged move generation (first TT move, then captures, then non-captures), MadChess collects:
Is Best Move (from TT)
Killer Score (0, 1, 2)
History Score
During move legality testing, MadChess collects IsCheck.
All of this is packed into a ulong Move with the following structure:
More important move aspects are packed into higher-order bits. This has the advantage of enabling sorting of moves by priority simply by sorting their ulong value. Sorting is done in stages (sort captures during the capture move generation stage, sort non-captures during their stage).
Chessnut1071 wrote: ↑Mon Dec 27, 2021 7:38 pmWhen you develop the list of pseudo moves how much information do you take along with the piece and move... In addition to piece and move, I take capture/defender, move direction, check, double check, discovered check, check direction, attacker, attacker location and ent passant.
I collect similar information minus some check details. During pseudo-legal move generation, MadChess collects:
From Square
To Square
Victim (or Piece.None)
Attacker (the moving piece: Piece.Pawn, Piece.Knight, etc)
Piece-specific properties (En Passant, Pawn Move, Double Pawn Move, Promoted Piece, King Move, Castling)
During staged move generation (first TT move, then captures, then non-captures), MadChess collects:
Is Best Move (from TT)
Killer Score (0, 1, 2)
History Score
During move legality testing, MadChess collects IsCheck.
All of this is packed into a ulong Move with the following structure:
More important move aspects are packed into higher-order bits. This has the advantage of enabling sorting of moves by priority simply by sorting their ulong value. Sorting is done in stages (sort captures during the capture move generation stage, sort non-captures during their stage).
Great example, I'm going to convert my 11 byte structure to a UInt64 byte. Learn something new here every day. thx
Chessnut1071 wrote: ↑Tue Dec 28, 2021 1:35 am
Great example, I'm going to convert my 11 byte structure to a UInt64 byte. Learn something new here every day. thx
Glad to help. Note that a ulong is 8 bytes = 64 bits. Perhaps you mean 11 ints = 11 * 4 bytes = 44 bytes? Compacting moves to a ulong should improve your engine's move generation speed. I have no idea how much.
Refer to the Move class to see how to get data in and out of the structure via static methods that apply bit masks and shifts.
Chessnut1071 wrote: ↑Tue Dec 28, 2021 1:35 am
Great example, I'm going to convert my 11 byte structure to a UInt64 byte. Learn something new here every day. thx
Glad to help. Note that a ulong is 8 bytes = 64 bits. Perhaps you mean 11 ints = 11 * 4 bytes = 44 bytes? Compacting moves to a ulong should improve your engine's move generation speed. I have no idea how much.
Refer to the Move class to see how to get data in and out of the structure via static methods that apply bit masks and shifts.
I need a total of 48 bits for the data I store, so, I have 12 more bits for new stuff. This has been a huge transition for me. I was working in integers when I first joined. After reading the benefits of bitboards, I started working in bytes and bits. This was like a new language for me, but, the speed is absolutely incredible. I have some problems which my computer takes 136 days to solve using my older engines. This is like a new found toy.
Chessnut1071 wrote: ↑Tue Dec 28, 2021 1:35 am
Great example, I'm going to convert my 11 byte structure to a UInt64 byte. Learn something new here every day. thx
Glad to help. Note that a ulong is 8 bytes = 64 bits. Perhaps you mean 11 ints = 11 * 4 bytes = 44 bytes? Compacting moves to a ulong should improve your engine's move generation speed. I have no idea how much.
Refer to the Move class to see how to get data in and out of the structure via static methods that apply bit masks and shifts.
Just finished my bitboard engine although it's the first draft and not quite optimum yet. My integer application of mailbox averaged 2.94 seconds per game solution. The bitboard averaged .045 seconds, or, over 65wx faster on the same solutions. UNBELIEVABLE. I probably had serious inefficiencies with my old engine since it was basically integer arrays with dozens of loops. Going to bitboards forced me to work in bits & bytes instead of integers. Bit operations seem to eliminate those loops for very fast bit shifts. I couldn't believe the speed increase; however, it's still nowhere near the speed of most of you on this board. I'm getting closer. I'm not sure where to go next although I'm going to research magic bitboards, since they're supposed to be even faster. The thing I like most about bitboards is the reduction in sort time, one 64-bit variable instead of 12 integer fields per record. Since the sort requirement gets more intensive the higher the ply level, it should significantly reduce the speed even further.
In spite of the significant increase in speed, I still need to get over 100x this speed for my objective, otherwise, I have problems.