## knodes/s halved after improving eval

Discussion of chess software programming and technical issues.

Moderators: hgm, Dann Corbit, Harvey Williamson

Forum rules
This textbox is used to restore diagrams posted with the [d] tag before the upgrade.
SrcEngine
Posts: 6
Joined: Sun Mar 22, 2020 2:04 pm
Full name: Luke Kong

### knodes/s halved after improving eval

I am new to chess programming. My engine used to run at 5000 knodes/s with a very simple eval. I've recently decided to improve the static evaluation functions. After adding no more than 100 lines of code, the engine is now only running at 2200knodes/s... Here is my updated EvalWhiteKnight function, which used to only evaluate based on a piecesquare table,

Code: Select all

``````int EvalWhiteKnight(const S_BOARD *pos)
{
int pceNum;
int index;
int sq;
int tempSq;
int score=0;
//loop through all the knights
for(pceNum = 0; pceNum < pos->pceNum[wN]; ++pceNum) {
sq = pos->pList[wN][pceNum];
//knight mobility
//loops through all the knight moves for the given knight
for(index = 0; index < 8; ++index) {
tempSq = sq + KnDir[index];
if(SqOnBoard(tempSq)) {
//bonus if the piece controls one of the center squares
if(tempSq == D4 || tempSq == D5 || tempSq==E4 || tempSq==E5)
{
score += knightCenterControl;
}
//if the move is controlled by an enemy pawn, ignore the move
if(pos->pieces[tempSq+11] != bP && pos->pieces[tempSq+9] != bP)
//otherwise add mobility score to the knight
score += knightMobility;
}
}

//undefended minor piece penalty

if(!SqAttacked(sq,WHITE,pos))
score += undefendedMinor;
//the knight needs to be in the enemy half of the board
if(RanksBrd[sq]>RANK_4)
{
//no opposing black pawns to push the knight away, and the knight is supported by a white pawn
(pos->pieces[sq-9]==wP || pos->pieces[sq-11]==wP))
{
//add bonus to this knight for having an outpost
score += outpost;
}
}
}
return score;
}``````
I've changed my bishop evaluations as well, which also only used to evaluate based on piece square tables,

Code: Select all

``````int EvalWhiteBishop(const S_BOARD *pos)
{
int pceNum;
int sq;
int index;
int t_sq;
int score=0;
//loop through all the white bishops
for(pceNum = 0; pceNum < pos->pceNum[wB]; ++pceNum) {
sq = pos->pList[wB][pceNum];
//loop through all the bishop move directions
for(index = 0; index < 4; ++index) {
int dir = BiDir[index];
t_sq = sq + dir;
//loop through all the moves in each direction
while(SqOnBoard(t_sq)) {
//if the bishop is controlling the center, add bonus
if(t_sq == D4 || t_sq == D5 || t_sq==E4 || t_sq==E5)
{
score += bishopCenterControl;
}
//if the bishop is behind a black pawn chain, stop going through this direction
if(pos->pieces[t_sq]==bP)
{
if(pos->pieces[t_sq+11] == bP || pos->pieces[t_sq+9] == bP) {
break;
}
}
//similarly to white pawn chains
if(pos->pieces[t_sq]==wP)
{
if(pos->pieces[t_sq-11] == wP || pos->pieces[t_sq-9] == wP) {
break;
}
}

t_sq += dir;

//add mobility score to the bishop
score += bishopMobility;
}
}
}
//bishop pair
if(pos->pceNum[wB]>1)
score += BishopPair;

return score;
}``````
Is this drastic speed reduction normal? Or have I messed something up terribly...? I could not pinpoint anything in my code that is crazy computationally expensive. I can't imagine what the speed is gonna be after I update all of my evaluation functions...

brianr
Posts: 507
Joined: Thu Mar 09, 2006 2:01 pm

### Re: knodes/s halved after improving eval

Yes, it is expected that doing more in the eval will slow things down quite a lot.
What counts is how well it plays.
Mobility with computing moves and looking at squares attacked is quite expensive.
There were some posts at one point about the relative value of various mobility terms.
The baseline (fastest nps but worst play) was something like only piece values plus piece square table values.
Passed pawns are probably pretty important, IIRC.
Many eval terms only add a little bit (80/20 rule).
Experiment.
My engine Tinker's eval was always pretty poor, so endgame tablebases helped a lot and I did not bother to add much endgame knowledge. Another area that is up to you.

Suggest using a robust match testing methodology (repeat each position changing sides, etc).
Get a decent opening book and use a tool like cutechess-cli and Ordo.
Do a "sanity check" first playing two identical copies of your engine against each other (perhaps from separate directories). The results should be VERY close to 50/50.

Eval changes can be tested with fixed nodes per move games.
Search changes are generally tested with some time per move (which should be fast, but not too fast that there are time forfeits).

Caution: Computer chess can become addicting.

jorose
Posts: 322
Joined: Thu Jan 22, 2015 2:21 pm
Location: Zurich, Switzerland
Full name: Jonathan Rosenthal

### Re: knodes/s halved after improving eval

Eval changes can be tested with fixed nodes per move games.
Just wanted to chime in here to mention that Brian probably is referring to small changes and tweaks to the evaluation function. Larger changes can effect how fast the engine can search nodes, as you described finding out yourself in this thread. In general I don't think its a bad idea to just always test with time and not nodes per move.
-Jonathan

brianr
Posts: 507
Joined: Thu Mar 09, 2006 2:01 pm

### Re: knodes/s halved after improving eval

Yes, thanks for pointing this out.
Perhaps I've been spending too much time with Leela where nets of the same size can use fixed nodes.

Of course, if the new eval terms might take longer to score than the knowledge is worth, then timed games should be used. For A/B engines with IID surprisingly fast games and small increments can be used.
If the change is just a value in a table or a weight factor, then the fixed nodes option is reasonable.

Kotlov
Posts: 232
Joined: Fri Jul 10, 2015 7:23 pm
Location: Russia

### Re: knodes/s halved after improving eval

brianr wrote:
Tue Jun 30, 2020 11:10 pm
Caution: Computer chess can become addicting.
confirm
Eugene Kotlov
Hedgehog 2.1 64-bit coming soon...

hgm
Posts: 26108
Joined: Fri Mar 10, 2006 9:06 am
Location: Amsterdam
Full name: H G Muller
Contact:

### Re: knodes/s halved after improving eval

If you calculate mobility by generating all moves, and checking some conditions after they would be made, it is almost like adding an extra ply to your search. Without increasing the node count. So of course your nodes/sec will take a hefty hit.

I can add that there would be more efficient methods for calculating what you do here. Whether Knight moves hit the center isn't dependent on any aspects of the position, and could have been incorporated in the piece-square table at engine startup, rather than recalculating it in every node. To know whether you want to count the Knight moves for the safe mobility requires you to check 2 board squares for Pawn presence, for each of 8 moves for each of 2 Knights, so 32 tests on the board in total. While there are at most 8 Pawns. If you would run through the Pawn list, using the location relative to that of a Kinght in a table lookup that tells you how many of the Knight moves that would attack, you would only need 8 x 2 = 16 tests. At worst; this would go down as Pawns get traded away, while the method you used would always keep checking 16 squares per Knight even when the opponent has no Pawns left at all.

For the Bishops it is a bit more tricky, as their moves can be blocked. So you cannot be sure whether these hit the center or a square under Pawn attack just by the location of the Bishop or the relative location to the Pawn. This seems to make explicit ray scanning like you do necessary. You could, however, keep that information at hand in a 'view-distance table' that you update incrementally: view[square][direction] would, for any occupied square, contain the distance to the next occupied square (or board edge) in each of the 8 directions. To get the mobility you then only have to add the view distances for the diagonal directions. (And to generate captures for that Bishop you would only have to test whether there is an enemy that distance away, without examining any intermediate board squares.)

Then you could also use a method similar to that for the Knight: run through the Pawn list, use the relative distance to the Bishops to look into a table if this makes the Pawn attack a square on a diagonal through the Bishop, and if so, in a second table, at what distance. Then you only have to compare that distance to the view distance of the Bishop in that direction to know whether you should discount some Bishop mobility. That might sound complex, but the secret is of course that you would hardly have to do any of it at all, as the first test (attacking the Bishops diagonal) would already fail for most Pawns. And you only have to test each Pawn against one Bishop, depending on the shade it is on. So if a Bishop on average would have 8 moves (like the Knight), instead of testing 2 x 8 x 2 = 32 board squares for Pawn presence, you would test relative Bishop location on 8 Pawns, and then occasionally do a bit extra. And again the work would go down as Pawns disappear.

SrcEngine
Posts: 6
Joined: Sun Mar 22, 2020 2:04 pm
Full name: Luke Kong

### Re: knodes/s halved after improving eval

Thank you all so much for taking the time to reply! I disliked my mobility code as well, but I didn't think it would impact the performance that much... I will replace the loops now with some lookup tables, and I will definitely incorporate some of the suggestions yall pointed out.