diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index c6563eadf2f..44e0596eab0 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -1,8 +1,8 @@ { "config": [ { - "name": "Ubuntu 20.04 GCC", - "os": "ubuntu-20.04", + "name": "Ubuntu 22.04 GCC", + "os": "ubuntu-22.04", "simple_name": "ubuntu", "compiler": "g++", "comp": "gcc", @@ -111,7 +111,7 @@ { "binaries": "x86-64-avxvnni", "config": { - "ubuntu-20.04": null + "ubuntu-22.04": null } }, { @@ -153,7 +153,7 @@ { "binaries": "apple-silicon", "config": { - "os": "ubuntu-20.04" + "os": "ubuntu-22.04" } } ] diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 452c2f2a30f..ab6b4350ec2 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -18,7 +18,7 @@ permissions: jobs: Clang-Format: name: Clang-Format - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b97aaa29c5d..57d0d53f0e2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,15 +13,15 @@ jobs: fail-fast: false matrix: config: - - name: Ubuntu 20.04 GCC - os: ubuntu-20.04 + - name: Ubuntu 22.04 GCC + os: ubuntu-22.04 compiler: g++ comp: gcc run_32bit_tests: true run_64bit_tests: true shell: bash - - name: Ubuntu 20.04 Clang - os: ubuntu-20.04 + - name: Ubuntu 22.04 Clang + os: ubuntu-22.04 compiler: clang++ comp: clang run_32bit_tests: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index caffc916e60..0b6fbce0d07 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ further discussion._ - Provide a clear and concise description of the changes in the pull request description. -_First time contributors should add their name to [AUTHORS](../AUTHORS)._ +_First time contributors should add their name to [AUTHORS](./AUTHORS)._ _Stockfish's development is not focused on adding new features. Thus any pull request introducing new features will potentially be closed without further @@ -86,7 +86,6 @@ more details. Thank you for contributing to Stockfish and helping us make it even better! - [copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt [discord-link]: https://discord.gg/GWDRS3kU6R [discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index ed5fc9af093..132bd6f484f 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -76,7 +76,7 @@ case $uname_s in case $uname_m in 'arm64') true_arch='apple-silicon' - file_arch='x86-64-sse41-popcnt' # Supported by Rosetta 2 + file_arch='m1-apple-silicon' ;; 'x86_64') flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.') diff --git a/src/movepick.cpp b/src/movepick.cpp index 8448616949d..c762e7e453c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -162,6 +162,7 @@ void MovePicker::score() { m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to]; m.value += (*continuationHistory[3])[pc][to]; + m.value += (*continuationHistory[4])[pc][to] / 3; m.value += (*continuationHistory[5])[pc][to]; // bonus for checks @@ -186,8 +187,7 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = - PieceValue[pos.piece_on(m.to_sq())] - type_of(pos.moved_piece(m)) + (1 << 28); + m.value = PieceValue[pos.piece_on(m.to_sq())] + (1 << 28); else m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index cbeb507f083..248e19dd09a 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -38,17 +38,99 @@ namespace Stockfish::Eval::NNUE::Layers { #if (USE_SSSE3 | (USE_NEON >= 8)) -alignas(CacheLineSize) static inline const - std::array, 256> lookup_indices = []() { - std::array, 256> v{}; - for (unsigned i = 0; i < 256; ++i) - { - std::uint64_t j = i, k = 0; - while (j) - v[i][k++] = pop_lsb(j); - } - return v; - }(); + + #if (USE_SSE41) +alignas(CacheLineSize) static constexpr std::uint8_t + #else +alignas(CacheLineSize) static constexpr std::uint16_t + #endif + lookup_indices[256][8] = { + {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 0, 0, 0, 0, 0, 0}, {2, 0, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 0}, + {1, 2, 0, 0, 0, 0, 0, 0}, {0, 1, 2, 0, 0, 0, 0, 0}, {3, 0, 0, 0, 0, 0, 0, 0}, + {0, 3, 0, 0, 0, 0, 0, 0}, {1, 3, 0, 0, 0, 0, 0, 0}, {0, 1, 3, 0, 0, 0, 0, 0}, + {2, 3, 0, 0, 0, 0, 0, 0}, {0, 2, 3, 0, 0, 0, 0, 0}, {1, 2, 3, 0, 0, 0, 0, 0}, + {0, 1, 2, 3, 0, 0, 0, 0}, {4, 0, 0, 0, 0, 0, 0, 0}, {0, 4, 0, 0, 0, 0, 0, 0}, + {1, 4, 0, 0, 0, 0, 0, 0}, {0, 1, 4, 0, 0, 0, 0, 0}, {2, 4, 0, 0, 0, 0, 0, 0}, + {0, 2, 4, 0, 0, 0, 0, 0}, {1, 2, 4, 0, 0, 0, 0, 0}, {0, 1, 2, 4, 0, 0, 0, 0}, + {3, 4, 0, 0, 0, 0, 0, 0}, {0, 3, 4, 0, 0, 0, 0, 0}, {1, 3, 4, 0, 0, 0, 0, 0}, + {0, 1, 3, 4, 0, 0, 0, 0}, {2, 3, 4, 0, 0, 0, 0, 0}, {0, 2, 3, 4, 0, 0, 0, 0}, + {1, 2, 3, 4, 0, 0, 0, 0}, {0, 1, 2, 3, 4, 0, 0, 0}, {5, 0, 0, 0, 0, 0, 0, 0}, + {0, 5, 0, 0, 0, 0, 0, 0}, {1, 5, 0, 0, 0, 0, 0, 0}, {0, 1, 5, 0, 0, 0, 0, 0}, + {2, 5, 0, 0, 0, 0, 0, 0}, {0, 2, 5, 0, 0, 0, 0, 0}, {1, 2, 5, 0, 0, 0, 0, 0}, + {0, 1, 2, 5, 0, 0, 0, 0}, {3, 5, 0, 0, 0, 0, 0, 0}, {0, 3, 5, 0, 0, 0, 0, 0}, + {1, 3, 5, 0, 0, 0, 0, 0}, {0, 1, 3, 5, 0, 0, 0, 0}, {2, 3, 5, 0, 0, 0, 0, 0}, + {0, 2, 3, 5, 0, 0, 0, 0}, {1, 2, 3, 5, 0, 0, 0, 0}, {0, 1, 2, 3, 5, 0, 0, 0}, + {4, 5, 0, 0, 0, 0, 0, 0}, {0, 4, 5, 0, 0, 0, 0, 0}, {1, 4, 5, 0, 0, 0, 0, 0}, + {0, 1, 4, 5, 0, 0, 0, 0}, {2, 4, 5, 0, 0, 0, 0, 0}, {0, 2, 4, 5, 0, 0, 0, 0}, + {1, 2, 4, 5, 0, 0, 0, 0}, {0, 1, 2, 4, 5, 0, 0, 0}, {3, 4, 5, 0, 0, 0, 0, 0}, + {0, 3, 4, 5, 0, 0, 0, 0}, {1, 3, 4, 5, 0, 0, 0, 0}, {0, 1, 3, 4, 5, 0, 0, 0}, + {2, 3, 4, 5, 0, 0, 0, 0}, {0, 2, 3, 4, 5, 0, 0, 0}, {1, 2, 3, 4, 5, 0, 0, 0}, + {0, 1, 2, 3, 4, 5, 0, 0}, {6, 0, 0, 0, 0, 0, 0, 0}, {0, 6, 0, 0, 0, 0, 0, 0}, + {1, 6, 0, 0, 0, 0, 0, 0}, {0, 1, 6, 0, 0, 0, 0, 0}, {2, 6, 0, 0, 0, 0, 0, 0}, + {0, 2, 6, 0, 0, 0, 0, 0}, {1, 2, 6, 0, 0, 0, 0, 0}, {0, 1, 2, 6, 0, 0, 0, 0}, + {3, 6, 0, 0, 0, 0, 0, 0}, {0, 3, 6, 0, 0, 0, 0, 0}, {1, 3, 6, 0, 0, 0, 0, 0}, + {0, 1, 3, 6, 0, 0, 0, 0}, {2, 3, 6, 0, 0, 0, 0, 0}, {0, 2, 3, 6, 0, 0, 0, 0}, + {1, 2, 3, 6, 0, 0, 0, 0}, {0, 1, 2, 3, 6, 0, 0, 0}, {4, 6, 0, 0, 0, 0, 0, 0}, + {0, 4, 6, 0, 0, 0, 0, 0}, {1, 4, 6, 0, 0, 0, 0, 0}, {0, 1, 4, 6, 0, 0, 0, 0}, + {2, 4, 6, 0, 0, 0, 0, 0}, {0, 2, 4, 6, 0, 0, 0, 0}, {1, 2, 4, 6, 0, 0, 0, 0}, + {0, 1, 2, 4, 6, 0, 0, 0}, {3, 4, 6, 0, 0, 0, 0, 0}, {0, 3, 4, 6, 0, 0, 0, 0}, + {1, 3, 4, 6, 0, 0, 0, 0}, {0, 1, 3, 4, 6, 0, 0, 0}, {2, 3, 4, 6, 0, 0, 0, 0}, + {0, 2, 3, 4, 6, 0, 0, 0}, {1, 2, 3, 4, 6, 0, 0, 0}, {0, 1, 2, 3, 4, 6, 0, 0}, + {5, 6, 0, 0, 0, 0, 0, 0}, {0, 5, 6, 0, 0, 0, 0, 0}, {1, 5, 6, 0, 0, 0, 0, 0}, + {0, 1, 5, 6, 0, 0, 0, 0}, {2, 5, 6, 0, 0, 0, 0, 0}, {0, 2, 5, 6, 0, 0, 0, 0}, + {1, 2, 5, 6, 0, 0, 0, 0}, {0, 1, 2, 5, 6, 0, 0, 0}, {3, 5, 6, 0, 0, 0, 0, 0}, + {0, 3, 5, 6, 0, 0, 0, 0}, {1, 3, 5, 6, 0, 0, 0, 0}, {0, 1, 3, 5, 6, 0, 0, 0}, + {2, 3, 5, 6, 0, 0, 0, 0}, {0, 2, 3, 5, 6, 0, 0, 0}, {1, 2, 3, 5, 6, 0, 0, 0}, + {0, 1, 2, 3, 5, 6, 0, 0}, {4, 5, 6, 0, 0, 0, 0, 0}, {0, 4, 5, 6, 0, 0, 0, 0}, + {1, 4, 5, 6, 0, 0, 0, 0}, {0, 1, 4, 5, 6, 0, 0, 0}, {2, 4, 5, 6, 0, 0, 0, 0}, + {0, 2, 4, 5, 6, 0, 0, 0}, {1, 2, 4, 5, 6, 0, 0, 0}, {0, 1, 2, 4, 5, 6, 0, 0}, + {3, 4, 5, 6, 0, 0, 0, 0}, {0, 3, 4, 5, 6, 0, 0, 0}, {1, 3, 4, 5, 6, 0, 0, 0}, + {0, 1, 3, 4, 5, 6, 0, 0}, {2, 3, 4, 5, 6, 0, 0, 0}, {0, 2, 3, 4, 5, 6, 0, 0}, + {1, 2, 3, 4, 5, 6, 0, 0}, {0, 1, 2, 3, 4, 5, 6, 0}, {7, 0, 0, 0, 0, 0, 0, 0}, + {0, 7, 0, 0, 0, 0, 0, 0}, {1, 7, 0, 0, 0, 0, 0, 0}, {0, 1, 7, 0, 0, 0, 0, 0}, + {2, 7, 0, 0, 0, 0, 0, 0}, {0, 2, 7, 0, 0, 0, 0, 0}, {1, 2, 7, 0, 0, 0, 0, 0}, + {0, 1, 2, 7, 0, 0, 0, 0}, {3, 7, 0, 0, 0, 0, 0, 0}, {0, 3, 7, 0, 0, 0, 0, 0}, + {1, 3, 7, 0, 0, 0, 0, 0}, {0, 1, 3, 7, 0, 0, 0, 0}, {2, 3, 7, 0, 0, 0, 0, 0}, + {0, 2, 3, 7, 0, 0, 0, 0}, {1, 2, 3, 7, 0, 0, 0, 0}, {0, 1, 2, 3, 7, 0, 0, 0}, + {4, 7, 0, 0, 0, 0, 0, 0}, {0, 4, 7, 0, 0, 0, 0, 0}, {1, 4, 7, 0, 0, 0, 0, 0}, + {0, 1, 4, 7, 0, 0, 0, 0}, {2, 4, 7, 0, 0, 0, 0, 0}, {0, 2, 4, 7, 0, 0, 0, 0}, + {1, 2, 4, 7, 0, 0, 0, 0}, {0, 1, 2, 4, 7, 0, 0, 0}, {3, 4, 7, 0, 0, 0, 0, 0}, + {0, 3, 4, 7, 0, 0, 0, 0}, {1, 3, 4, 7, 0, 0, 0, 0}, {0, 1, 3, 4, 7, 0, 0, 0}, + {2, 3, 4, 7, 0, 0, 0, 0}, {0, 2, 3, 4, 7, 0, 0, 0}, {1, 2, 3, 4, 7, 0, 0, 0}, + {0, 1, 2, 3, 4, 7, 0, 0}, {5, 7, 0, 0, 0, 0, 0, 0}, {0, 5, 7, 0, 0, 0, 0, 0}, + {1, 5, 7, 0, 0, 0, 0, 0}, {0, 1, 5, 7, 0, 0, 0, 0}, {2, 5, 7, 0, 0, 0, 0, 0}, + {0, 2, 5, 7, 0, 0, 0, 0}, {1, 2, 5, 7, 0, 0, 0, 0}, {0, 1, 2, 5, 7, 0, 0, 0}, + {3, 5, 7, 0, 0, 0, 0, 0}, {0, 3, 5, 7, 0, 0, 0, 0}, {1, 3, 5, 7, 0, 0, 0, 0}, + {0, 1, 3, 5, 7, 0, 0, 0}, {2, 3, 5, 7, 0, 0, 0, 0}, {0, 2, 3, 5, 7, 0, 0, 0}, + {1, 2, 3, 5, 7, 0, 0, 0}, {0, 1, 2, 3, 5, 7, 0, 0}, {4, 5, 7, 0, 0, 0, 0, 0}, + {0, 4, 5, 7, 0, 0, 0, 0}, {1, 4, 5, 7, 0, 0, 0, 0}, {0, 1, 4, 5, 7, 0, 0, 0}, + {2, 4, 5, 7, 0, 0, 0, 0}, {0, 2, 4, 5, 7, 0, 0, 0}, {1, 2, 4, 5, 7, 0, 0, 0}, + {0, 1, 2, 4, 5, 7, 0, 0}, {3, 4, 5, 7, 0, 0, 0, 0}, {0, 3, 4, 5, 7, 0, 0, 0}, + {1, 3, 4, 5, 7, 0, 0, 0}, {0, 1, 3, 4, 5, 7, 0, 0}, {2, 3, 4, 5, 7, 0, 0, 0}, + {0, 2, 3, 4, 5, 7, 0, 0}, {1, 2, 3, 4, 5, 7, 0, 0}, {0, 1, 2, 3, 4, 5, 7, 0}, + {6, 7, 0, 0, 0, 0, 0, 0}, {0, 6, 7, 0, 0, 0, 0, 0}, {1, 6, 7, 0, 0, 0, 0, 0}, + {0, 1, 6, 7, 0, 0, 0, 0}, {2, 6, 7, 0, 0, 0, 0, 0}, {0, 2, 6, 7, 0, 0, 0, 0}, + {1, 2, 6, 7, 0, 0, 0, 0}, {0, 1, 2, 6, 7, 0, 0, 0}, {3, 6, 7, 0, 0, 0, 0, 0}, + {0, 3, 6, 7, 0, 0, 0, 0}, {1, 3, 6, 7, 0, 0, 0, 0}, {0, 1, 3, 6, 7, 0, 0, 0}, + {2, 3, 6, 7, 0, 0, 0, 0}, {0, 2, 3, 6, 7, 0, 0, 0}, {1, 2, 3, 6, 7, 0, 0, 0}, + {0, 1, 2, 3, 6, 7, 0, 0}, {4, 6, 7, 0, 0, 0, 0, 0}, {0, 4, 6, 7, 0, 0, 0, 0}, + {1, 4, 6, 7, 0, 0, 0, 0}, {0, 1, 4, 6, 7, 0, 0, 0}, {2, 4, 6, 7, 0, 0, 0, 0}, + {0, 2, 4, 6, 7, 0, 0, 0}, {1, 2, 4, 6, 7, 0, 0, 0}, {0, 1, 2, 4, 6, 7, 0, 0}, + {3, 4, 6, 7, 0, 0, 0, 0}, {0, 3, 4, 6, 7, 0, 0, 0}, {1, 3, 4, 6, 7, 0, 0, 0}, + {0, 1, 3, 4, 6, 7, 0, 0}, {2, 3, 4, 6, 7, 0, 0, 0}, {0, 2, 3, 4, 6, 7, 0, 0}, + {1, 2, 3, 4, 6, 7, 0, 0}, {0, 1, 2, 3, 4, 6, 7, 0}, {5, 6, 7, 0, 0, 0, 0, 0}, + {0, 5, 6, 7, 0, 0, 0, 0}, {1, 5, 6, 7, 0, 0, 0, 0}, {0, 1, 5, 6, 7, 0, 0, 0}, + {2, 5, 6, 7, 0, 0, 0, 0}, {0, 2, 5, 6, 7, 0, 0, 0}, {1, 2, 5, 6, 7, 0, 0, 0}, + {0, 1, 2, 5, 6, 7, 0, 0}, {3, 5, 6, 7, 0, 0, 0, 0}, {0, 3, 5, 6, 7, 0, 0, 0}, + {1, 3, 5, 6, 7, 0, 0, 0}, {0, 1, 3, 5, 6, 7, 0, 0}, {2, 3, 5, 6, 7, 0, 0, 0}, + {0, 2, 3, 5, 6, 7, 0, 0}, {1, 2, 3, 5, 6, 7, 0, 0}, {0, 1, 2, 3, 5, 6, 7, 0}, + {4, 5, 6, 7, 0, 0, 0, 0}, {0, 4, 5, 6, 7, 0, 0, 0}, {1, 4, 5, 6, 7, 0, 0, 0}, + {0, 1, 4, 5, 6, 7, 0, 0}, {2, 4, 5, 6, 7, 0, 0, 0}, {0, 2, 4, 5, 6, 7, 0, 0}, + {1, 2, 4, 5, 6, 7, 0, 0}, {0, 1, 2, 4, 5, 6, 7, 0}, {3, 4, 5, 6, 7, 0, 0, 0}, + {0, 3, 4, 5, 6, 7, 0, 0}, {1, 3, 4, 5, 6, 7, 0, 0}, {0, 1, 3, 4, 5, 6, 7, 0}, + {2, 3, 4, 5, 6, 7, 0, 0}, {0, 2, 3, 4, 5, 6, 7, 0}, {1, 2, 3, 4, 5, 6, 7, 0}, + {0, 1, 2, 3, 4, 5, 6, 7}}; // Find indices of nonzero numbers in an int32_t array template @@ -74,7 +156,11 @@ void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_ou using vec128_t = __m128i; #define vec128_zero _mm_setzero_si128() #define vec128_set_16(a) _mm_set1_epi16(a) - #define vec128_load(a) _mm_load_si128(a) + #if (USE_SSE41) + #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) + #else + #define vec128_load(a) _mm_load_si128(a) + #endif #define vec128_storeu(a, b) _mm_storeu_si128(a, b) #define vec128_add(a, b) _mm_add_epi16(a, b) #elif defined(USE_NEON) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 8649d9521bb..14fdecd720f 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -449,8 +449,8 @@ class FeatureTransformer { void hint_common_access(const Position& pos, AccumulatorCaches::Cache* cache) const { - hint_common_access_for_perspective(pos, cache); - hint_common_access_for_perspective(pos, cache); + update_accumulator(pos, cache); + update_accumulator(pos, cache); } private: @@ -821,31 +821,12 @@ class FeatureTransformer { entry.byTypeBB[pt] = pos.pieces(pt); } - template - void hint_common_access_for_perspective(const Position& pos, - AccumulatorCaches::Cache* cache) const { - - // Works like update_accumulator, but performs less work. - // Updates ONLY the accumulator for pos. - - // Look for a usable accumulator of an earlier position. We keep track - // of the estimated gain in terms of features to be added/subtracted. - // Fast early exit. - if ((pos.state()->*accPtr).computed[Perspective]) - return; - - StateInfo* oldest = try_find_computed_accumulator(pos); - - if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state()) - update_accumulator_incremental(pos, oldest); - else - update_accumulator_refresh_cache(pos, cache); - } template void update_accumulator(const Position& pos, AccumulatorCaches::Cache* cache) const { - + if ((pos.state()->*accPtr).computed[Perspective]) + return; StateInfo* oldest = try_find_computed_accumulator(pos); if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state()) diff --git a/src/position.cpp b/src/position.cpp index 9d883c92b47..49f520e0118 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -689,7 +689,12 @@ bool Position::gives_check(Move m) const { // Makes a move, and saves all information necessary // to a StateInfo object. The move is assumed to be legal. Pseudo-legal // moves should be filtered out before this function is called. -void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { +// If a pointer to the TT table is passed, the entry for the new position +// will be prefetched +void Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -887,11 +892,13 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } - // Set capture piece - st->capturedPiece = captured; - // Update the key with the final value st->key = k; + if (tt) + prefetch(tt->first_entry(key())); + + // Set capture piece + st->capturedPiece = captured; // Calculate checkers bitboard (if move gives check) st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; @@ -1069,23 +1076,6 @@ void Position::undo_null_move() { } -// Computes the new hash key after the given move. Needed -// for speculative prefetch. It doesn't recognize special moves like castling, -// en passant and promotions. -Key Position::key_after(Move m) const { - - Square from = m.from_sq(); - Square to = m.to_sq(); - Piece pc = piece_on(from); - Piece captured = piece_on(to); - Key k = st->key ^ Zobrist::side; - - k ^= Zobrist::psq[captured][to] ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; - - return (captured || type_of(pc) == PAWN) ? k : adjust_key50(k); -} - - // Tests if the SEE (Static Exchange Evaluation) // value of move is greater or equal to the given threshold. We'll use an // algorithm similar to alpha-beta pruning with a null window. diff --git a/src/position.h b/src/position.h index 7fdaf84d5fe..0d49a60a9fb 100644 --- a/src/position.h +++ b/src/position.h @@ -141,8 +141,8 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt); - void do_move(Move m, StateInfo& newSt, bool givesCheck); + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + void do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); void undo_move(Move m); void do_null_move(StateInfo& newSt, const TranspositionTable& tt); void undo_null_move(); @@ -152,7 +152,6 @@ class Position { // Accessing hash keys Key key() const; - Key key_after(Move m) const; Key material_key() const; Key pawn_key() const; Key major_piece_key() const; @@ -369,7 +368,9 @@ inline void Position::move_piece(Square from, Square to) { board[to] = pc; } -inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); } +inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { + do_move(m, newSt, gives_check(m), tt); +} inline StateInfo* Position::state() const { return st; } diff --git a/src/search.cpp b/src/search.cpp index c27d93d2565..e99d5d3fc9f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -119,7 +119,8 @@ void update_all_stats(const Position& pos, Square prevSq, ValueList& quietsSearched, ValueList& capturesSearched, - Depth depth); + Depth depth, + bool isTTMove); } // namespace @@ -247,10 +248,14 @@ void Search::Worker::iterative_deepening() { &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel (ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0]; (ss - i)->staticEval = VALUE_NONE; + (ss - i)->reduction = 0; } for (int i = 0; i <= MAX_PLY + 2; ++i) - (ss + i)->ply = i; + { + (ss + i)->ply = i; + (ss + i)->reduction = 0; + } ss->pv = pv; @@ -441,17 +446,19 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - int nodesEffort = rootMoves[0].effort * 100 / std::max(size_t(1), size_t(nodes)); + int nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); - double fallingEval = (11 + 2 * (mainThread->bestPreviousAverageScore - bestValue) - + (mainThread->iterValue[iterIdx] - bestValue)) - / 100.0; - fallingEval = std::clamp(fallingEval, 0.580, 1.667); + double fallingEval = + (11.396 + 2.035 * (mainThread->bestPreviousAverageScore - bestValue) + + 0.968 * (mainThread->iterValue[iterIdx] - bestValue)) + / 100.0; + fallingEval = std::clamp(fallingEval, 0.5786, 1.6752); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.495 : 0.687; - double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); - double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.4857 : 0.7046; + double reduction = + (1.4540 + mainThread->previousTimeReduction) / (2.1593 * timeReduction); + double bestMoveInstability = 0.9929 + 1.8519 * totBestMoveChanges / threads.size(); double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; @@ -462,7 +469,7 @@ void Search::Worker::iterative_deepening() { auto elapsedTime = elapsed(); - if (completedDepth >= 10 && nodesEffort >= 97 && elapsedTime > totalTime * 0.739 + if (completedDepth >= 10 && nodesEffort >= 97056 && elapsedTime > totalTime * 0.6540 && !mainThread->ponder) threads.stop = true; @@ -477,7 +484,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.506; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.5138; } mainThread->iterValue[iterIdx] = bestValue; @@ -567,6 +574,8 @@ Value Search::Worker::search( Value bestValue, value, eval, maxValue, probCutBeta; bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, ttCapture; + int priorReduction = ss->reduction; + ss->reduction = 0; Piece movedPiece; ValueList capturesSearched; @@ -772,19 +781,20 @@ Value Search::Worker::search( opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; + if (priorReduction >= 3 && !opponentWorsening) + depth++; + // Step 7. Razoring (~1 Elo) - // If eval is really low, check with qsearch if we can exceed alpha. If the - // search suggests we cannot exceed alpha, return a speculative fail low. + // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. if (!PvNode && eval < alpha - 462 - 297 * depth * depth) - return qsearch(pos, ss, alpha - 1, alpha); + return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. if (!ss->ttPv && depth < 14 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 310 - + (ss->staticEval == eval) * (40 - std::abs(correctionValue) / 131072) + - (ss - 1)->statScore / 310 + 40 - std::abs(correctionValue) / 131072 >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; @@ -848,7 +858,7 @@ Value Search::Worker::search( // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. probCutBeta = beta + 174 - 56 * improving; - if (depth > 3 + if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt // probCut there and in further interactions with transposition table cutoff @@ -859,6 +869,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); + Depth probCutDepth = std::max(depth - 4, 0); while ((move = mp.next_move()) != Move::none()) { @@ -872,25 +883,24 @@ Value Search::Worker::search( assert(pos.capture_stage(move)); - // Prefetch the TT entry for the resulting position - prefetch(tt.first_entry(pos.key_after(move))); + movedPiece = pos.moved_piece(move); + + pos.do_move(move, st, &tt); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); ss->currentMove = move; ss->continuationHistory = - &this->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; + &this->continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = - &this->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()]; - - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - pos.do_move(move, st); + &this->continuationCorrectionHistory[movedPiece][move.to_sq()]; // Perform a preliminary qsearch to verify that the move holds value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); // If the qsearch held, perform the regular search - if (value >= probCutBeta) - value = - -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, depth - 4, !cutNode); + if (value >= probCutBeta && probCutDepth > 0) + value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, probCutDepth, + !cutNode); pos.undo_move(move); @@ -898,7 +908,7 @@ Value Search::Worker::search( { // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, - depth - 3, move, unadjustedStaticEval, tt.generation()); + probCutDepth + 1, move, unadjustedStaticEval, tt.generation()); if (!is_decisive(value)) return value - (probCutBeta - beta); @@ -914,12 +924,9 @@ Value Search::Worker::search( && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) return probCutBeta; - const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, - (ss - 2)->continuationHistory, - (ss - 3)->continuationHistory, - (ss - 4)->continuationHistory, - nullptr, - (ss - 6)->continuationHistory}; + const PieceToHistory* contHist[] = { + (ss - 1)->continuationHistory, (ss - 2)->continuationHistory, (ss - 3)->continuationHistory, + (ss - 4)->continuationHistory, (ss - 5)->continuationHistory, (ss - 6)->continuationHistory}; MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->lowPlyHistory, @@ -972,6 +979,10 @@ Value Search::Worker::search( Depth r = reduction(improving, depth, moveCount, delta); + // Decrease reduction if position is or has been on the PV (~7 Elo) + if (ss->ttPv) + r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; + // Step 14. Pruning at shallow depth (~120 Elo). // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) @@ -1118,12 +1129,13 @@ Value Search::Worker::search( extension = 1; } + // Step 16. Make the move + pos.do_move(move, st, givesCheck, &tt); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + // Add extension to new depth newDepth += extension; - // Speculative prefetch as early as possible - prefetch(tt.first_entry(pos.key_after(move))); - // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->continuationHistory = @@ -1132,19 +1144,11 @@ Value Search::Worker::search( &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; - // Step 16. Make the move - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - pos.do_move(move, st, givesCheck); - // These reduction adjustments have proven non-linear scaling. // They are optimized to time controls of 180 + 1.8 and longer, // so changing them or adding conditions that are similar requires // tests at these types of time controls. - // Decrease reduction if position is or has been on the PV (~7 Elo) - if (ss->ttPv) - r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; - // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) r -= 1018; @@ -1192,10 +1196,16 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. + + Depth d = std::max( 1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))); - value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); + (ss + 1)->reduction = newDepth - d; + + value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); + (ss + 1)->reduction = 0; + // Do a full-depth search when reduced LMR search fails high if (value > alpha && d < newDepth) @@ -1369,7 +1379,8 @@ Value Search::Worker::search( // If there is a move that produces search value greater than alpha, // we update the stats of searched moves. else if (bestMove) - update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth); + update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, + bestMove == ttData.move); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1637,20 +1648,19 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) continue; } - // Speculative prefetch as early as possible - prefetch(tt.first_entry(pos.key_after(move))); + // Step 7. Make and search the move + Piece movedPiece = pos.moved_piece(move); + + pos.do_move(move, st, givesCheck, &tt); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Update the current move ss->currentMove = move; ss->continuationHistory = - &thisThread - ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = - &thisThread->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()]; + &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; - // Step 7. Make and search the move - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - pos.do_move(move, st, givesCheck); value = -qsearch(pos, ss + 1, -beta, -alpha); pos.undo_move(move); @@ -1789,13 +1799,14 @@ void update_all_stats(const Position& pos, Square prevSq, ValueList& quietsSearched, ValueList& capturesSearched, - Depth depth) { + Depth depth, + bool isTTMove) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = stat_bonus(depth); + int bonus = stat_bonus(depth) + 300 * isTTMove; int malus = stat_malus(depth); if (!pos.capture_stage(bestMove)) @@ -1831,8 +1842,8 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed by moves // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - static constexpr std::array conthist_bonuses = { - {{1, 1025}, {2, 621}, {3, 325}, {4, 512}, {6, 534}}}; + static constexpr std::array conthist_bonuses = { + {{1, 1025}, {2, 621}, {3, 325}, {4, 512}, {5, 122}, {6, 534}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -2123,7 +2134,7 @@ void SearchManager::pv(Search::Worker& worker, if (!isExact) info.bound = bound; - TimePoint time = tm.elapsed_time() + 1; + TimePoint time = std::max(TimePoint(1), tm.elapsed_time()); info.timeMs = time; info.nodes = nodes; info.nps = nodes * 1000 / time; @@ -2148,7 +2159,7 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po if (pv[0] == Move::none()) return false; - pos.do_move(pv[0], st); + pos.do_move(pv[0], st, &tt); auto [ttHit, ttData, ttWriter] = tt.probe(pos.key()); if (ttHit) diff --git a/src/search.h b/src/search.h index dee759410ba..3983e0f33c6 100644 --- a/src/search.h +++ b/src/search.h @@ -74,6 +74,7 @@ struct Stack { bool ttPv; bool ttHit; int cutoffCnt; + int reduction; }; diff --git a/src/timeman.cpp b/src/timeman.cpp index d0b0d0a9492..d073a84a963 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -88,17 +88,19 @@ void TimeManagement::init(Search::LimitsType& limits, const TimePoint scaledInc = limits.inc[us] / scaleFactor; // Maximum move horizon of 50 moves - int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; // If less than one second, gradually reduce mtg - if (scaledTime < 1000 && double(mtg) / scaledInc > 0.05) + if (scaledTime < 1000 && double(centiMTG) / scaledInc > 5.051) { - mtg = scaledTime * 0.05; + centiMTG = scaledTime * 5.051; } // Make sure timeLeft is > 0 since we may use it as a divisor - TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) - - moveOverhead * (2 + mtg)); + TimePoint timeLeft = + std::max(TimePoint(1), + limits.time[us] + + (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100); // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed the actual available @@ -107,31 +109,32 @@ void TimeManagement::init(Search::LimitsType& limits, { // Extra time according to timeLeft if (originalTimeAdjust < 0) - originalTimeAdjust = 0.3285 * std::log10(timeLeft) - 0.4830; + originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; // Calculate time constants based on current time left. double logTimeInSec = std::log10(scaledTime / 1000.0); - double optConstant = std::min(0.00308 + 0.000319 * logTimeInSec, 0.00506); - double maxConstant = std::max(3.39 + 3.01 * logTimeInSec, 2.93); + double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); + double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); - optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant, - 0.213 * limits.time[us] / timeLeft) + optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, + 0.213035 * limits.time[us] / timeLeft) * originalTimeAdjust; - maxScale = std::min(6.64, maxConstant + ply / 12.0); + maxScale = std::min(6.67704, maxConstant + ply / 11.9847); } // x moves in y seconds (+ z increment) else { - optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / timeLeft); - maxScale = std::min(6.3, 1.5 + 0.11 * mtg); + optScale = + std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * limits.time[us] / timeLeft); + maxScale = 1.3 + 0.11 * (centiMTG / 100.0); } // Limit the maximum possible time for this move optimumTime = TimePoint(optScale * timeLeft); maximumTime = - TimePoint(std::min(0.825 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; if (options["Ponder"]) optimumTime += optimumTime / 4; diff --git a/src/tt.cpp b/src/tt.cpp index 50f5ca45af8..5d8457611dd 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -238,7 +238,9 @@ std::tuple TranspositionTable::probe(const Key key) cons > tte[i].depth8 - tte[i].relative_age(generation8) * 2) replace = &tte[i]; - return {false, TTData(), TTWriter(replace)}; + return {false, + TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_ENTRY_OFFSET, BOUND_NONE, false}, + TTWriter(replace)}; } diff --git a/src/tt.h b/src/tt.h index f0936fd282c..065380ca8ba 100644 --- a/src/tt.h +++ b/src/tt.h @@ -51,6 +51,15 @@ struct TTData { Depth depth; Bound bound; bool is_pv; + + TTData() = delete; + TTData(Move m, Value v, Value ev, Depth d, Bound b, bool pv) : + move(m), + value(v), + eval(ev), + depth(d), + bound(b), + is_pv(pv) {}; };