Hi,
I am creating my own C# bitboard engine from scratch and it is going pretty good so far, much thanks to this forum and other helpful resources.
Since C# is not my main language I would like some advice on how to speed things up in the base of the code, that is the move generation, make and unmake move functions.
Currently I have optimized the logic as far as I could and have gone from around 500 k nodes/s to 1.5 M nodes/s. My questions are:
1. I have the relevant code including bit operation code here: https://onecompiler.com/csharp/3zsymc4eh. I know it is a lot to ask, I would highly appreciate if you had the time to take a rough look at it to see if I make any obvious mistakes that could slow the performance down.
2. Is 1.5 M nodes/s okay for a C# engine for move generation, make and unmake move? I have no reference to what good values are for this langauge.
Thank you so much in advance, and hope I am not asking for too much or it breaks some rule in this forum
Increase performance of make move, unmake move and move generation functions
Moderators: chrisw, Rebel, Ras
-
- Posts: 114
- Joined: Sat Nov 14, 2020 12:49 pm
- Full name: Elias Nilsson
-
- Posts: 17
- Joined: Thu Jun 30, 2022 12:46 pm
- Full name: Kabir Kumar
Re: Increase performance of make move, unmake move and move generation functions
Is this speed of perft? If yes, then it is slow. From preliminary view, it seems that there are few unnecessary conditionals in make and unmake moves.
Also suggest to use struct if the object is compact and has all value types.
I will take another look at this later today if update.
-
- Posts: 114
- Joined: Sat Nov 14, 2020 12:49 pm
- Full name: Elias Nilsson
Re: Increase performance of make move, unmake move and move generation functions
Yes it is perft, in game it is around 800k-1M. Thank youokidoki wrote: ↑Thu Nov 09, 2023 6:18 pmIs this speed of perft? If yes, then it is slow. From preliminary view, it seems that there are few unnecessary conditionals in make and unmake moves.
Also suggest to use struct if the object is compact and has all value types.
I will take another look at this later today if update.
-
- Posts: 882
- Joined: Sun Dec 27, 2020 2:40 am
- Location: Bremen, Germany
- Full name: Thomas Jahn
Re: Increase performance of make move, unmake move and move generation functions
With perft it's important that you don't compare apples with oranges. Legal movegens are faster than pseudolegal because they can just count the number of legal moves at the leaf nodes without playing them. To further increase the speed you can use transpositon tables, or even multithreading and claim amazing perft speeds.
But that said 1.5M nodes is not very fast for perft even in the most basic of cases. My engine Leorik (also written in C#) computes perft with ~90M nodes per second on a i7-9700K. That's without any tricks: it generates pseudo-legal moves and checks their legality after playing them. And that happens at a rate of playing 90 million legal moves per second. There's no unmake though as I use copy/make.
https://github.com/lithander/Leorik/tre ... orik.Perft
We did a little experiment on that a while ago because the general expectation was that managed languages couldn't really compete with C/C++ performance wise. That seems to be true but the difference is much less than many would expect:
Code: Select all
C# version 1.8 with .Net 5
1361558651 Nodes, 22813ms, 59682K NPS
Java version with JDK 17
1361558651 Nodes, 28095ms, 48462K NPS
C version built with GCC via MinGW 64bit (gcc -Ofast -march=native -static)
1361558651 Nodes, 18963ms, 71800K NPS
-
- Posts: 290
- Joined: Mon Mar 13, 2006 5:23 pm
- Location: Québec
- Full name: Mathieu Pagé
Re: Increase performance of make move, unmake move and move generation functions
This makes me want to write an engine in C#. I really like C++ and the fact that it is possible to write highly optimized code, but there is a cost in complexity which is not negligible. C# code can be made much clearer and cleaner (in my opinion) than C# code.lithander wrote: ↑Fri Nov 10, 2023 1:10 pm We did a little experiment on that a while ago because the general expectation was that managed languages couldn't really compete with C/C++ performance wise. That seems to be true but the difference is much less than many would expect:
Code: Select all
C# version 1.8 with .Net 5 1361558651 Nodes, 22813ms, 59682K NPS Java version with JDK 17 1361558651 Nodes, 28095ms, 48462K NPS C version built with GCC via MinGW 64bit (gcc -Ofast -march=native -static) 1361558651 Nodes, 18963ms, 71800K NPS
A 20% loss in performance is probably around 30-40 ELO. It's important when you want to be the best and compete with Stockfish, but my ambition has never been to develop the best engine. For me it's a hobby and the objective is to have fun implementing and optimizing interesting algorithms and heuristics.
Sometimes I don't enjoy working on my engines because the code becomes complex and I often have to refactor parts of it to simplify them. Although it's normal to have to refactor code, I find that it's much easier for me to write clear and maintainable code in C# (in my professional life) than in C++ (for my chess engines).
Mathieu Pagé
mathieu@mathieupage.com
mathieu@mathieupage.com
-
- Posts: 882
- Joined: Sun Dec 27, 2020 2:40 am
- Location: Bremen, Germany
- Full name: Thomas Jahn
Re: Increase performance of make move, unmake move and move generation functions
Definitely give it a try then, especially if you're using C# professionally! I have been using C# since 2004 and felt like I pretty much hit a ceiling until I started with chess programming. When I tried to push the nps of my chess engine to the limits I picked up quite a few new tricks.mathmoi wrote: ↑Fri Nov 10, 2023 6:59 pm This makes me want to write an engine in C#. I really like C++ and the fact that it is possible to write highly optimized code, but there is a cost in complexity which is not negligible. C# code can be made much clearer and cleaner (in my opinion) than C# code.
A 20% loss in performance is probably around 30-40 ELO. It's important when you want to be the best and compete with Stockfish, but my ambition has never been to develop the best engine. For me it's a hobby and the objective is to have fun implementing and optimizing interesting algorithms and heuristics.
Sometimes I don't enjoy working on my engines because the code becomes complex and I often have to refactor parts of it to simplify them. Although it's normal to have to refactor code, I find that it's much easier for me to write clear and maintainable code in C# (in my professional life) than in C++ (for my chess engines).
I'm also quite happy with the code quality: Leorik is still clear and maintainable to me. But it's not trivial to make fast code in C#! The compiler will apply much less optimizations behind the scenes than in C/C++ as it is optimized for compilation-speed and a JIT environment. Arrays for example will always have bound checking, Lists and other containers have an entire abstraction layer on top of that. When you rely on the garbage collector you'll pay for it, too. So... how do you manage your data?
And there are a few other things that are inherently more expensive in C# than their C equivalent because C# has safety measures you can't just disable. So just doing literally the same thing you'd do in C++ is often not the fastest way of doing it in C#.
I personally refrain from using 'unsafe' code. But if you're okay with using unsafe you can use C# a bit more like it were C++.
...anyway, give it a try! It's fun! And the speed difference between a naive implementation that uses standard C# patterns and the optimized end result can be mindboggling. (MinimalChess is an example for the former)
-
- Posts: 15
- Joined: Thu Aug 08, 2013 5:13 pm
Re: Increase performance of make move, unmake move and move generation functions
A late reply on this:
I started a chess engine in C# which was reasonably fast. Using the 'new' [ConstExpected] arguments seemed to work reasonably well. But I gave up on it because of the JIT compiler (which makes first runs always slow) and the GC moving memory around, which was extremely frustrating
My latest try is progressing now, using Rust, and it is insanely fast. The perft is about 30 percent faster than Stockfish. Search is a long way off still...
I started a chess engine in C# which was reasonably fast. Using the 'new' [ConstExpected] arguments seemed to work reasonably well. But I gave up on it because of the JIT compiler (which makes first runs always slow) and the GC moving memory around, which was extremely frustrating
My latest try is progressing now, using Rust, and it is insanely fast. The perft is about 30 percent faster than Stockfish. Search is a long way off still...