Question About CPP-C#, Performance, and Square Represenation

Discussion of chess software programming and technical issues.

Moderator: Ras

User avatar
emadsen
Posts: 440
Joined: Thu Apr 26, 2012 1:51 am
Location: Oak Park, IL, USA
Full name: Erik Madsen

Re: Question About CPP-C#, Performance, and Square Represena

Post by emadsen »

That is really cool. Reminds me on coroutines. It seems Yield need to safe some state variables to "remember" which yield occurred last and to restore its scope and context later recursively on an extra stack for all those iterator objects along the current path searched.

How is Moves defined? List<Move>?
Gerd,

From what I've read about coroutines, they don't fit well into traditional stack-based languages, which favor subroutines (execute routine, return to caller, execute next instruction in caller).

The yield keyword instructs the C# compiler to add some "syntactic sugar" to enable the enumerator to "pick up where it left off," so to speak. So yes, yield must save and restore state. This is done, of course, so the programmer doesn't have to do it himself.

I am unsure of the performance penalty of yield in C# because I've never measured it. Frankly, I didn't want to write messy state-management code. Yield was free and easy, so I wrote an enumerator with yield and moved on to other problems.

Yes, Moves is defined as List<Move>.
Erik Madsen | My C# chess engine: https://www.madchess.net
rreagan
Posts: 102
Joined: Sun Sep 09, 2007 6:32 am

Re: Question About CPP-C#, Performance, and Square Represena

Post by rreagan »

Rein Halbersma wrote:Thanks, Erik, that's a really helpful explanation! Your code does what Fabien Letouzey was wishing for in this thread from last year. Would be nice to be able to do this in C++ as well.
The good old days, when Dann Corbit posted here :)
bpfliegel
Posts: 71
Joined: Fri Mar 16, 2012 10:16 am

Re: Question About CPP-C#, Performance, and Square Represena

Post by bpfliegel »

emadsen wrote:
That is really cool. Reminds me on coroutines. It seems Yield need to safe some state variables to "remember" which yield occurred last and to restore its scope and context later recursively on an extra stack for all those iterator objects along the current path searched.

How is Moves defined? List<Move>?
Gerd,

From what I've read about coroutines, they don't fit well into traditional stack-based languages, which favor subroutines (execute routine, return to caller, execute next instruction in caller).

The yield keyword instructs the C# compiler to add some "syntactic sugar" to enable the enumerator to "pick up where it left off," so to speak. So yes, yield must save and restore state. This is done, of course, so the programmer doesn't have to do it himself.

I am unsure of the performance penalty of yield in C# because I've never measured it. Frankly, I didn't want to write messy state-management code. Yield was free and easy, so I wrote an enumerator with yield and moved on to other problems.

Yes, Moves is defined as List<Move>.
I think Erik's implementation has no performance penalty as he is not using yield in the subgenerators.

What costs much is this line:
'Moves colMoves = new Moves();'

Cheers, Balint
User avatar
emadsen
Posts: 440
Joined: Thu Apr 26, 2012 1:51 am
Location: Oak Park, IL, USA
Full name: Erik Madsen

Re: Question About CPP-C#, Performance, and Square Represena

Post by emadsen »

think Erik's implementation has no performance penalty as he is not using yield in the subgenerators.

What costs much is this line:
'Moves colMoves = new Moves();'
I believe you're correct Balint. Plus all the new Move() statements. An important item on my to-do list is to review all allocations. I'm sure I'm taking a major performance hit there. Classic trade off with object-oriented design- simple code, not the best performance. I'd like to find a comfortable middle ground where I retain much of the simplicity of OO but improve performance.

I'll review your ObjectBroker code.
Erik Madsen | My C# chess engine: https://www.madchess.net
User avatar
emadsen
Posts: 440
Joined: Thu Apr 26, 2012 1:51 am
Location: Oak Park, IL, USA
Full name: Erik Madsen

Re: Question About CPP-C#, Performance, and Square Represena

Post by emadsen »

suggest you to create static array of the objects that you use frequently and then just assign values to free ones, instead of creating new objects. I use two array: one for nodes and one for moves. When i store a move, i write the data in a move object then increment the node index (or the node pointer) to access the next move.
Stefano,

Do you reuse moves? If so how do you track scope? That is, how do you know when a move is no longer referenced (further up the search tree) and can be safely reused?

Do you make use of finalizers or any other language constructs to track this? Or when you are out of moves in the move buffer, do you just allocate many at once?
Erik Madsen | My C# chess engine: https://www.madchess.net
bpfliegel
Posts: 71
Joined: Fri Mar 16, 2012 10:16 am

Re: Question About CPP-C#, Performance, and Square Represena

Post by bpfliegel »

emadsen wrote:
think Erik's implementation has no performance penalty as he is not using yield in the subgenerators.

What costs much is this line:
'Moves colMoves = new Moves();'
I believe you're correct Balint. Plus all the new Move() statements. An important item on my to-do list is to review all allocations. I'm sure I'm taking a major performance hit there. Classic trade off with object-oriented design- simple code, not the best performance. I'd like to find a comfortable middle ground where I retain much of the simplicity of OO but improve performance.

I'll review your ObjectBroker code.
Erik, I really like your OO style on RumbleMinze btw. But this is a must unfortunately. No 'new' statements when the engine runs, period. Your work on eval deserves to have performance (we .Net guys are already handicapped) and not allowing a no brainer engine to win only as it is able to dig 2 plies below you.

Best, Balint
Gerd Isenberg
Posts: 2251
Joined: Wed Mar 08, 2006 8:47 pm
Location: Hattingen, Germany

Re: Question About CPP-C#, Performance, and Square Represena

Post by Gerd Isenberg »

Behind the scenes of the C# yield keyword by Lars Corneliussen explains it quite good.
bpfliegel
Posts: 71
Joined: Fri Mar 16, 2012 10:16 am

Re: Question About CPP-C#, Performance, and Square Represena

Post by bpfliegel »

Gerd Isenberg wrote:Behind the scenes of the C# yield keyword by Lars Corneliussen explains it quite good.
I remember this test about performance:
http://blogs.msdn.com/b/irenak/archive/ ... 56898.aspx

If you check the comments, there is a high-res repro of the results. Losing 20% at one problem is nothing. One does it 10 times, it is much. If you gain 20% 10 times - that is something a lot better :) I remember starting from 160k in Portfish and my goal was 1M for NPS in search mode - fortunately by reaching that goal I was totally out of ideas :)

Anyway, best of luck,
Balint
Cheney
Posts: 104
Joined: Thu Sep 27, 2012 2:24 am

Re: Question About CPP-C#, Performance, and Square Represena

Post by Cheney »

Hi,

Thanks for the reply.

I may have found a few performance items but am stuck with a question on my mind :)

1. I iterated the 64 squares to locate pieces and then I'd generate that piece's moves. It appears I can iterate the bits in each pieces' bitboards to identify the piece and then generate moves. Thus, the iteration is less than looping all squares.

2. Every move was being logged as a new object move to a list (for game moves). I think all these "new" creations added too much overhead. It appears this was taking place during negamax and perft and making a huge list of unneeded move logs.

3. I think I use too many methods and started to consolidate them into one large method. For example, I start with a GenerateAllMoves and five methods deeper I am validating moves of a piece. I think it is too chatty :)

The question on my mind is if I should generate moves only for the side that is moving. I think yes but cannot quite see how to determine check. While generating moves, each square is checked for occupancy and if the king, it is set in check.

In the bitboard model, while generating moves, I create WhiteAttacked and BlackAttacked bitboard. I can AND either one with the opponent king to determine check. But to do this, after a move, I still have to regenerate that side's moves to help determine attacks/checks.

What I am stuck on is with this model of generating all moves for both sides, after a move is made, if I do not generate the moves again, I cannot detemine what that side attacks.

I read about an "IsAttackedBy" concept but it seems that to do so I would have to essentially create a reverse move list specifically for sliding pieces and that appears to be costly. Is this the way to go?

Thanks again :)
User avatar
emadsen
Posts: 440
Joined: Thu Apr 26, 2012 1:51 am
Location: Oak Park, IL, USA
Full name: Erik Madsen

Re: Question About CPP-C#, Performance, and Square Represena

Post by emadsen »

Cheney,

I wouldn't worry about #3. I doubt the overhead of subroutine calls is significant compared to other performance issues. Plus giant methods encourage code duplication and make code difficult to understand (too much to grasp at once).
The question on my mind is if I should generate moves only for the side that is moving. I think yes but cannot quite see how to determine check. While generating moves, each square is checked for occupancy and if the king, it is set in check.
You only need to generate moves for the SideToMove. All you need to know is whether the SideToMove king is in check. You don't need to generate all moves for the other side to determine this.

I can't comment on bitboard, but with mailbox you can start at the king's square and examine each of the eight directional rays until you a) find a friendly piece, b) find an illegal (out of bounds) square, or c) find an enemy piece- in which case the king is in check. As an optimization, you can increment / decrement a count of pieces on ranks, files, and up and down diagonals with each move to know whether a sliding attack is even possible. Of course you have to track relevant pieces (don't track bishops on ranks / files). And you have to check for pawn, enemy king, and knight attacks.
Erik Madsen | My C# chess engine: https://www.madchess.net