Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntaxic sugar for Position::see_ge(move, threshold) #5529

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

snicolet
Copy link
Member

@snicolet snicolet commented Aug 8, 2024

Syntactic sugar around Position::see_ge(), so that we can use the more readable pos.see(move) >= threshold instead of pos.see_ge(move, threshold), and similar readable code for the other comparison operators.

Patch tested locally for speed with both clang and gcc, no speed penalty found.

[Edit]
Non-regression test started here: https://tests.stockfishchess.org/tests/view/66b49f254ff211be9d4edf2a

No functional change.

Copy link

github-actions bot commented Aug 8, 2024

clang-format 18 needs to be run on this PR.
If you do not have clang-format installed, the maintainer will run it when merging.
For the exact version please see https://packages.ubuntu.com/noble/clang-format-18.

(execution 10300593128 / attempt 1)

@@ -238,7 +238,7 @@ Move MovePicker::next_move(bool skipQuiets) {
case GOOD_CAPTURE :
if (select<Next>([&]() {
// Move losing capture to endBadCaptures to be tried later
return pos.see_ge(*cur, -cur->value / 18) ? true
return pos.see(*cur) >= (-cur->value / 18) ? true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing PR!
Now one question I wanted to ask for so long, is that for such moves, can't we save the see value into the move struct such that we reuse it when we want to prune?, the code in search looks like redundant work to me, IIUC that should be a (nice) speedup.

Copy link
Member Author

@snicolet snicolet Aug 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there is no function in master (nor in this PR) to calculate the SEE value of a move, we can only compare that value to a lower bound via see_ge()

@snicolet
Copy link
Member Author

snicolet commented Aug 8, 2024

I am convinced that this is important stuff concerning the readability and the ease of reasoning about Static Exchange Evaluation of moves, when we write or tweak our see()-related pruning algorithms in search.cpp and movepicker.cpp.

There was a furry of see() pruning tests ten years ago, and much less these days... In retrospect the introduction of pos.see_ge() in #822 , albeit nice in itself for the speed it has given, has slowed down the creativity of the team for new see() improvements, just because the syntax is more painful and we subconsciously refuse to work with it.

Compare these two versions:

    current:      if (pos.see_ge(move)) ...
                  if (!pos.see_ge(move, 1))) ...
                  if (!pos.see_ge(move, -24 * lmrDepth * lmrDepth))...

    new:          if (pos.see(move) >= 0) ...
                  if (pos.see(move) <= 0) ...
                  if (pos.see(move) < -24 * lmrDepth * lmrDepth) ...

Honestly, are we able to tell without thinking what kind of comparison we do in the first version? Now, and also in six months when we come back to the code? It takes me about 5 seconds each time I read the code in master to get each line right, and 5 more seconds to double-check!

snicolet added a commit to snicolet/Stockfish that referenced this pull request Aug 8, 2024
Syntactic sugar around Position::see_ge(), so that we can use
the more readable pos.see(move) >= threshold instead of
pos.see_ge(move, threshold), and similar readable code for
the other comparison operators.

Closes official-stockfish/Stockfish#5529

No functional change.
@@ -1019,7 +1019,7 @@ Value Search::Worker::search(
lmrDepth = std::max(lmrDepth, 0);

// Prune moves with negative SEE (~4 Elo)
if (!pos.see_ge(move, -24 * lmrDepth * lmrDepth))
if (pos.see(move) < -24 * lmrDepth * lmrDepth)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there is no function in master (nor in this PR) to calculate the SEE value of a move, we can only compare that value to an upper bound or a lower bound via see_ge()

@snicolet I'm referring to here, aren't we calling the function again on the move, if we are passing move to the function aren't we calculating the result for such move in this position.. is this call not redundant to the ones in the movepicker?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we save the result S. and reuse it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a function, this is just syntaxic sugar...

pos.see(move) < A     

is strictly equivalent to

!pos.see_ge(move, A)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see so it's like alpha-beta?
So you are saying that the value is not exact for some reason,
is it not possible to match bounds at least, just something like the saved bounds in TT, where we at least match windows/thresholds?
&& (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)))

Copy link
Contributor

@peregrineshahin peregrineshahin Aug 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e. if a see_ge value fails high over +3000, is calculating see_ge of threshold -90000 relevant?

snicolet added a commit to snicolet/Stockfish that referenced this pull request Aug 8, 2024
Syntactic sugar around Position::see_ge(), so that we can use
the more readable pos.see(move) >= threshold instead of
pos.see_ge(move, threshold), and similar readable code for
the other comparison operators.

Closes official-stockfish/Stockfish#5529

No functional change.
@cj5716
Copy link
Contributor

cj5716 commented Aug 8, 2024

I think a non-reg STC would be good, because this has shown to be quite the slowdown for many engines

Edit: sorry, I misread and missed the operator overloading

Great stuff in this case

Copy link
Member

@Disservin Disservin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea imo, please use using instead of typedef to be consistent with the code

what i don't like is that there are now two ways of doing this and not one consistent one, i think it makes sense if the see_ge function is private and only useable over the see function, but then i also don't like that the return type of see() (the pair) is useable

something like this should cover both cases and prevent any misuse
https://godbolt.org/z/nve76Kh5x

@snicolet
Copy link
Member Author

snicolet commented Aug 8, 2024

I have tested speed locally with both clang and gcc, and found no speed penalty.

  1. two runs of bench at depth 22 in parallel (clang 11):
testedpatch
Total time (ms) : 59078
Nodes searched  : 38781020
Nodes/second    : 656437

master
Total time (ms) : 59239
Nodes searched  : 38781020
Nodes/second    : 654653
  1. two runs of bench at depth 22 in parallel (gcc 14)
testedpatch
Total time (ms) : 55748
Nodes searched  : 38781020
Nodes/second    : 695648

master
Total time (ms) : 55947
Nodes searched  : 38781020
Nodes/second    : 693174

snicolet added a commit to snicolet/Stockfish that referenced this pull request Aug 8, 2024
Syntactic sugar around Position::see_ge(), so that we can use
the more readable pos.see(move) >= threshold instead of
pos.see_ge(move, threshold), and similar readable code for
the other comparison operators.

Closes official-stockfish/Stockfish#5529

No functional change.
Syntactic sugar around Position::see_ge(), so that we can use
the more readable pos.see(move) >= threshold instead of
pos.see_ge(move, threshold), and similar readable code for
the other comparison operators.

Patch tested locally for speed with both clang and gcc, no speed
penalty found.

Non-regression test started here:
https://tests.stockfishchess.org/tests/view/66b49f254ff211be9d4edf2a

Closes official-stockfish#5529

No functional change.
@peregrineshahin
Copy link
Contributor

Sorry about the noise I know this PR is only related to syntaxtic sugar but I was wondering about the bigger picture,
here is a practical proof of what I claim to be redundent work:
master...peregrineshahin:Stockfish:seeStuff1
We indeed have redundent logic.

// SEE based pruning for captures and checks (~11 Elo)
int  seeHist          = std::clamp(captHist / 32, -182 * depth, 166 * depth);
auto extMove          = mp.current();
int  captThreshold    = -168 * depth - seeHist;
int  extMoveThreshold = -extMove->value / 18;

if (extMove && ((mp.stage == GOOD_CAPTURE && extMoveThreshold > captThreshold)))
    dbg_hit_on(!pos.see_ge(move, captThreshold), 0);

if (extMove && ((mp.stage == BAD_CAPTURE && extMoveThreshold < captThreshold)))
    dbg_hit_on(pos.see_ge(move, captThreshold), 1);

// Hit #0: Total 16445 Hits 0 Hit Rate (%) 0
// Hit #1: Total 14913 Hits 0 Hit Rate (%) 0

// ===========================
// Total time (ms) : 3730
// Nodes searched  : 1277466

if (!pos.see_ge(move, -168 * depth - seeHist))
    continue;

@cj5716
Copy link
Contributor

cj5716 commented Aug 8, 2024

There is no guarantee where a good capture will beat the SEE threshold (and vice versa), though, because the way we determine good captures in movepicker is not a fixed value but relies on capture history

@peregrineshahin
Copy link
Contributor

peregrineshahin commented Aug 8, 2024

Yes, so what you do is this to skip the work.......
You can access the movepicker score in search as I demonstarted

if (mp.stage == GOOD_CAPTURE && extMoveThreshold >= captThreshold)
{}
else if ((mp.stage == BAD_CAPTURE && extMoveThreshold <= captThreshold) || !pos.see_ge(move, captThreshold))
    continue;

@cj5716
Copy link
Contributor

cj5716 commented Aug 8, 2024

ah, I see what you mean. that could be a possible speedup

@xu-shawn
Copy link
Contributor

The non-regression test had passed:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 110144 W: 28527 L: 28388 D: 53229
Ptnml(0-2): 370, 12568, 29086, 12649, 399

https://tests.stockfishchess.org/tests/view/66b293024ff211be9d4edd7e

@Disservin
Copy link
Member

just wondering would it make sense if we move see out of position? after all it is an evaluation (static exchange evaluation)

see https://github.com/official-stockfish/Stockfish/compare/master...Disservin:Stockfish:see-sugar-smth?expand=1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants