From 2d19cf7b8651b22a90f32b21bbf7b15c977c4a74 Mon Sep 17 00:00:00 2001 From: wopdevries Date: Fri, 4 Sep 2026 09:49:39 +0200 Subject: [PATCH 1/6] instrument: TT lookup/hit counters (hot-path) + env-gated summary --- library/src/ab_search.cpp | 7 +++++++ library/src/solver_if.cpp | 28 ++++++++++++++++++++++++++++ library/src/system/thread_data.hpp | 3 +++ 3 files changed, 38 insertions(+) diff --git a/library/src/ab_search.cpp b/library/src/ab_search.cpp index a055f4bba..27b950dca 100644 --- a/library/src/ab_search.cpp +++ b/library/src/ab_search.cpp @@ -56,6 +56,13 @@ auto apply_ab_tt_lookup( limit, lowerFlag); TIMER_END(TIMER_NO_LOOKUP, depth); + // Instrumentation: per-thread TT lookup/hit counters + if (thrp) { + ++thrp->tt_lookup_count; + if (cardsP) + ++thrp->tt_hit_count; + } + if (!cardsP) return false; diff --git a/library/src/solver_if.cpp b/library/src/solver_if.cpp index f935f004e..edab80554 100644 --- a/library/src/solver_if.cpp +++ b/library/src/solver_if.cpp @@ -670,6 +670,20 @@ auto solve_board_internal( futp->nodes = ctx.search().trick_nodes(); } + // Print TT stats if requested + if (std::getenv("DDS_PRINT_TT_STATS")) { + ThreadData* thrp_ptr = ctx.thread_ptr(); + if (thrp_ptr && thrp_ptr->tt_lookup_count > 0) { + double hit_rate = 100.0 * (double)thrp_ptr->tt_hit_count / + (double)thrp_ptr->tt_lookup_count; + std::fprintf(stderr, + "DDS_TT_STATS: lookups=%llu hits=%llu hit_rate=%.2f%%\n", + (unsigned long long)thrp_ptr->tt_lookup_count, + (unsigned long long)thrp_ptr->tt_hit_count, + hit_rate); + } + } + #ifdef DDS_MEMORY_LEAKS_WIN32 _CrtDumpMemoryLeaks(); #endif @@ -811,6 +825,20 @@ auto solve_same_board( futp->nodes = ctx.search().trick_nodes(); } + // Print TT stats if requested + if (std::getenv("DDS_PRINT_TT_STATS")) { + ThreadData* thrp_ptr = ctx.thread_ptr(); + if (thrp_ptr && thrp_ptr->tt_lookup_count > 0) { + double hit_rate = 100.0 * (double)thrp_ptr->tt_hit_count / + (double)thrp_ptr->tt_lookup_count; + std::fprintf(stderr, + "DDS_TT_STATS: lookups=%llu hits=%llu hit_rate=%.2f%%\n", + (unsigned long long)thrp_ptr->tt_lookup_count, + (unsigned long long)thrp_ptr->tt_hit_count, + hit_rate); + } + } + #ifdef DDS_MEMORY_LEAKS_WIN32 _CrtDumpMemoryLeaks(); #endif diff --git a/library/src/system/thread_data.hpp b/library/src/system/thread_data.hpp index a9b1035d0..d4d2f797a 100644 --- a/library/src/system/thread_data.hpp +++ b/library/src/system/thread_data.hpp @@ -61,6 +61,9 @@ struct ThreadData double memUsed; int nodes; int trickNodes; + // TT instrumentation (per-context, single-threaded) + uint64_t tt_lookup_count = 0; + uint64_t tt_hit_count = 0; // Constant for a given hand. // 960 KB From 75b067cf574c31f4306ff00b7c20b747bf5aadaa Mon Sep 17 00:00:00 2001 From: wopdevries Date: Sat, 5 Sep 2026 06:07:52 +0200 Subject: [PATCH 2/6] fix: reset TT counters per-solve; add add/overwrite counters to PageStats --- library/src/solver_if.cpp | 6 ++++++ library/src/trans_table/trans_table_l.cpp | 5 ++++- library/src/trans_table/trans_table_l.hpp | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/library/src/solver_if.cpp b/library/src/solver_if.cpp index edab80554..b1c3fe7cb 100644 --- a/library/src/solver_if.cpp +++ b/library/src/solver_if.cpp @@ -284,6 +284,8 @@ auto solve_board_internal( thrp->ABStats.Reset(); thrp->ABStats.ResetCum(); #endif + thrp->tt_lookup_count = 0; + thrp->tt_hit_count = 0; #ifdef DDS_TOP_LEVEL { @@ -735,6 +737,8 @@ auto solve_same_board( thrp->ABStats.Reset(); thrp->ABStats.ResetCum(); #endif + thrp->tt_lookup_count = 0; + thrp->tt_hit_count = 0; #ifdef DDS_TOP_LEVEL { @@ -933,6 +937,8 @@ auto analyse_later_board( thrp->ABStats.Reset(); thrp->ABStats.ResetCum(); #endif + thrp->tt_lookup_count = 0; + thrp->tt_hit_count = 0; #ifdef DDS_TOP_LEVEL { diff --git a/library/src/trans_table/trans_table_l.cpp b/library/src/trans_table/trans_table_l.cpp index e8dea203c..c5c12404d 100644 --- a/library/src/trans_table/trans_table_l.cpp +++ b/library/src/trans_table/trans_table_l.cpp @@ -209,7 +209,7 @@ TransTableL::TransTableL() pages_maximum_ = 0; harvest_trick_ = 0; harvest_hand_ = 0; - page_stats_ = PageStats{0,0,0,0,0}; + page_stats_ = PageStats{0,0,0,0,0,0,0}; timestamp_ = 0; pool_ = nullptr; next_block_ = nullptr; @@ -931,7 +931,10 @@ auto TransTableL::create_or_update( return; } + // Instrumentation: count new insertions and overwrites + page_stats_.num_adds_++; if (n == BlocksPerEntry) { + page_stats_.num_overwrites_++; if (bp->next_write_no_ >= BlocksPerEntry) bp->next_write_no_ = 0; } diff --git a/library/src/trans_table/trans_table_l.hpp b/library/src/trans_table/trans_table_l.hpp index 34c7a2b8f..625af462a 100644 --- a/library/src/trans_table/trans_table_l.hpp +++ b/library/src/trans_table/trans_table_l.hpp @@ -142,6 +142,8 @@ class TransTableL: public TransTable int num_frees_; ///< Total deallocations int num_harvests_; ///< Total harvest operations int last_current_; ///< Last current page number + int num_adds_; ///< Total new entries inserted + int num_overwrites_; ///< Insertions that overwrote existing entries }; /// \brief Harvested blocks saved for potential reuse (16 bytes). From dbbb974ad8ab4b215084747532c4985fa4956dd6 Mon Sep 17 00:00:00 2001 From: wopdevries Date: Sun, 6 Sep 2026 16:57:52 +0200 Subject: [PATCH 3/6] fix: add includes, reset op_stats per-solve, reset new PageStats fields --- library/src/solver_if.cpp | 23 +++++++++++++++++++++++ library/src/system/thread_data.hpp | 1 + library/src/trans_table/trans_table.hpp | 4 ++++ library/src/trans_table/trans_table_l.cpp | 2 ++ library/src/trans_table/trans_table_l.hpp | 10 ++++++++++ library/src/trans_table/trans_table_s.hpp | 4 ++++ 6 files changed, 44 insertions(+) diff --git a/library/src/solver_if.cpp b/library/src/solver_if.cpp index b1c3fe7cb..b4972b69f 100644 --- a/library/src/solver_if.cpp +++ b/library/src/solver_if.cpp @@ -7,6 +7,8 @@ See LICENSE and README. */ +#include +#include #include #include #include @@ -286,6 +288,7 @@ auto solve_board_internal( #endif thrp->tt_lookup_count = 0; thrp->tt_hit_count = 0; + if (auto* tt = ctx.trans_table()) tt->reset_op_stats(); #ifdef DDS_TOP_LEVEL { @@ -683,6 +686,15 @@ auto solve_board_internal( (unsigned long long)thrp_ptr->tt_lookup_count, (unsigned long long)thrp_ptr->tt_hit_count, hit_rate); + if (auto* tt = ctx.trans_table()) { + int adds, overwrites, harvests; + tt->get_op_stats(adds, overwrites, harvests); + double ow_rate = adds > 0 ? + 100.0 * (double)overwrites / (double)adds : 0.0; + std::fprintf(stderr, + "DDS_TT_STATS: adds=%d overwrites=%d overwrite_rate=%.2f%% harvests=%d\n", + adds, overwrites, ow_rate, harvests); + } } } @@ -739,6 +751,7 @@ auto solve_same_board( #endif thrp->tt_lookup_count = 0; thrp->tt_hit_count = 0; + if (auto* tt = ctx.trans_table()) tt->reset_op_stats(); #ifdef DDS_TOP_LEVEL { @@ -840,6 +853,15 @@ auto solve_same_board( (unsigned long long)thrp_ptr->tt_lookup_count, (unsigned long long)thrp_ptr->tt_hit_count, hit_rate); + if (auto* tt = ctx.trans_table()) { + int adds, overwrites, harvests; + tt->get_op_stats(adds, overwrites, harvests); + double ow_rate = adds > 0 ? + 100.0 * (double)overwrites / (double)adds : 0.0; + std::fprintf(stderr, + "DDS_TT_STATS: adds=%d overwrites=%d overwrite_rate=%.2f%% harvests=%d\n", + adds, overwrites, ow_rate, harvests); + } } } @@ -939,6 +961,7 @@ auto analyse_later_board( #endif thrp->tt_lookup_count = 0; thrp->tt_hit_count = 0; + if (auto* tt = ctx.trans_table()) tt->reset_op_stats(); #ifdef DDS_TOP_LEVEL { diff --git a/library/src/system/thread_data.hpp b/library/src/system/thread_data.hpp index d4d2f797a..909b1b4da 100644 --- a/library/src/system/thread_data.hpp +++ b/library/src/system/thread_data.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef DDS_AB_STATS #include "ab_stats.hpp" diff --git a/library/src/trans_table/trans_table.hpp b/library/src/trans_table/trans_table.hpp index 88e64c119..c96ad73cd 100644 --- a/library/src/trans_table/trans_table.hpp +++ b/library/src/trans_table/trans_table.hpp @@ -225,6 +225,10 @@ class TransTable virtual auto print_all_suit_stats(std::ofstream& fout) const -> void = 0; /// \brief Print summary suit statistics. + /// \brief Get add/overwrite/harvest counters for instrumentation. + virtual auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void = 0; + virtual auto reset_op_stats() -> void = 0; + virtual auto print_summary_suit_stats(std::ofstream& fout) const -> void = 0; /// \brief Print entries distribution for a specific hand. diff --git a/library/src/trans_table/trans_table_l.cpp b/library/src/trans_table/trans_table_l.cpp index c5c12404d..5e8adf3d9 100644 --- a/library/src/trans_table/trans_table_l.cpp +++ b/library/src/trans_table/trans_table_l.cpp @@ -470,6 +470,8 @@ auto TransTableL::return_all_memory() -> void page_stats_.num_frees_ = 0; page_stats_.num_harvests_ = 0; page_stats_.last_current_ = 0; + page_stats_.num_adds_ = 0; + page_stats_.num_overwrites_ = 0; TransTableL::release_tt(); diff --git a/library/src/trans_table/trans_table_l.hpp b/library/src/trans_table/trans_table_l.hpp index 625af462a..47dac22ad 100644 --- a/library/src/trans_table/trans_table_l.hpp +++ b/library/src/trans_table/trans_table_l.hpp @@ -465,6 +465,16 @@ class TransTableL: public TransTable /// \brief Print summary suit statistics. /// /// \param fout Output stream + auto reset_op_stats() -> void override { + page_stats_.num_adds_ = 0; + page_stats_.num_overwrites_ = 0; + page_stats_.num_harvests_ = 0; + } + auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override { + adds = page_stats_.num_adds_; + overwrites = page_stats_.num_overwrites_; + harvests = page_stats_.num_harvests_; + } auto print_summary_suit_stats(std::ofstream& fout) const -> void override; /// \brief Print entries for a specific hand distribution. diff --git a/library/src/trans_table/trans_table_s.hpp b/library/src/trans_table/trans_table_s.hpp index f044018b2..a7d3d746b 100644 --- a/library/src/trans_table/trans_table_s.hpp +++ b/library/src/trans_table/trans_table_s.hpp @@ -299,6 +299,10 @@ class TransTableS: public TransTable auto print_all_suit_stats(std::ofstream& /*fout*/) const -> void override { } + auto reset_op_stats() -> void override {} + auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override { + adds = 0; overwrites = 0; harvests = 0; // TransTableS not instrumented + } auto print_summary_suit_stats(std::ofstream& /*fout*/) const -> void override { } From 3401a365c10408be92caf6062ec24a6e4b9364c4 Mon Sep 17 00:00:00 2001 From: wopdevries Date: Mon, 7 Sep 2026 18:03:47 +0200 Subject: [PATCH 4/6] fix: add get_op_stats and reset_op_stats to MockTransTable --- library/tests/trans_table/trans_table_base_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/tests/trans_table/trans_table_base_test.cpp b/library/tests/trans_table/trans_table_base_test.cpp index 8834a91cf..4a671fea2 100644 --- a/library/tests/trans_table/trans_table_base_test.cpp +++ b/library/tests/trans_table/trans_table_base_test.cpp @@ -37,6 +37,8 @@ class TransTableBaseTest : public ::testing::Test MockTransTable() : TransTable() {} ~MockTransTable() override = default; + auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override { adds = 0; overwrites = 0; harvests = 0; } + auto reset_op_stats() -> void override {} void init(const int handLookup[][15]) override { From edc48e50a1bd0a865f45967a12cb9833a23c2629 Mon Sep 17 00:00:00 2001 From: wopdevries Date: Wed, 9 Sep 2026 00:35:44 +0200 Subject: [PATCH 5/6] fix: Allman style, strict env var check, doxygen for reset_op_stats --- library/src/solver_if.cpp | 4 ++-- library/src/trans_table/trans_table.hpp | 2 ++ library/src/trans_table/trans_table_l.hpp | 6 ++++-- library/src/trans_table/trans_table_s.hpp | 11 ++++++++--- library/tests/trans_table/trans_table_base_test.cpp | 11 +++++++++-- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/library/src/solver_if.cpp b/library/src/solver_if.cpp index b4972b69f..c9702f191 100644 --- a/library/src/solver_if.cpp +++ b/library/src/solver_if.cpp @@ -676,7 +676,7 @@ auto solve_board_internal( } // Print TT stats if requested - if (std::getenv("DDS_PRINT_TT_STATS")) { + if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && std::string(env) == "1") { ThreadData* thrp_ptr = ctx.thread_ptr(); if (thrp_ptr && thrp_ptr->tt_lookup_count > 0) { double hit_rate = 100.0 * (double)thrp_ptr->tt_hit_count / @@ -843,7 +843,7 @@ auto solve_same_board( } // Print TT stats if requested - if (std::getenv("DDS_PRINT_TT_STATS")) { + if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && std::string(env) == "1") { ThreadData* thrp_ptr = ctx.thread_ptr(); if (thrp_ptr && thrp_ptr->tt_lookup_count > 0) { double hit_rate = 100.0 * (double)thrp_ptr->tt_hit_count / diff --git a/library/src/trans_table/trans_table.hpp b/library/src/trans_table/trans_table.hpp index c96ad73cd..c57be3b4f 100644 --- a/library/src/trans_table/trans_table.hpp +++ b/library/src/trans_table/trans_table.hpp @@ -227,6 +227,8 @@ class TransTable /// \brief Print summary suit statistics. /// \brief Get add/overwrite/harvest counters for instrumentation. virtual auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void = 0; + + /// \brief Reset add/overwrite/harvest counters for per-solve stats. virtual auto reset_op_stats() -> void = 0; virtual auto print_summary_suit_stats(std::ofstream& fout) const -> void = 0; diff --git a/library/src/trans_table/trans_table_l.hpp b/library/src/trans_table/trans_table_l.hpp index 47dac22ad..ad6e981b3 100644 --- a/library/src/trans_table/trans_table_l.hpp +++ b/library/src/trans_table/trans_table_l.hpp @@ -465,12 +465,14 @@ class TransTableL: public TransTable /// \brief Print summary suit statistics. /// /// \param fout Output stream - auto reset_op_stats() -> void override { + auto reset_op_stats() -> void override + { page_stats_.num_adds_ = 0; page_stats_.num_overwrites_ = 0; page_stats_.num_harvests_ = 0; } - auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override { + auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override + { adds = page_stats_.num_adds_; overwrites = page_stats_.num_overwrites_; harvests = page_stats_.num_harvests_; diff --git a/library/src/trans_table/trans_table_s.hpp b/library/src/trans_table/trans_table_s.hpp index a7d3d746b..6560c225f 100644 --- a/library/src/trans_table/trans_table_s.hpp +++ b/library/src/trans_table/trans_table_s.hpp @@ -299,9 +299,14 @@ class TransTableS: public TransTable auto print_all_suit_stats(std::ofstream& /*fout*/) const -> void override { } - auto reset_op_stats() -> void override {} - auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override { - adds = 0; overwrites = 0; harvests = 0; // TransTableS not instrumented + auto reset_op_stats() -> void override + { + } + auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override + { + adds = 0; + overwrites = 0; + harvests = 0; // TransTableS not instrumented } auto print_summary_suit_stats(std::ofstream& /*fout*/) const -> void override { diff --git a/library/tests/trans_table/trans_table_base_test.cpp b/library/tests/trans_table/trans_table_base_test.cpp index 4a671fea2..420a4e258 100644 --- a/library/tests/trans_table/trans_table_base_test.cpp +++ b/library/tests/trans_table/trans_table_base_test.cpp @@ -37,8 +37,15 @@ class TransTableBaseTest : public ::testing::Test MockTransTable() : TransTable() {} ~MockTransTable() override = default; - auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override { adds = 0; overwrites = 0; harvests = 0; } - auto reset_op_stats() -> void override {} + auto get_op_stats(int& adds, int& overwrites, int& harvests) const -> void override + { + adds = 0; + overwrites = 0; + harvests = 0; + } + auto reset_op_stats() -> void override + { + } void init(const int handLookup[][15]) override { From bb87ea10a7d607da30e064b1da713862ba01241f Mon Sep 17 00:00:00 2001 From: wopdevries Date: Thu, 10 Sep 2026 09:33:38 +0200 Subject: [PATCH 6/6] fix: use PRIu64 and avoid string allocation in env var check --- library/src/solver_if.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/library/src/solver_if.cpp b/library/src/solver_if.cpp index c9702f191..8001a492e 100644 --- a/library/src/solver_if.cpp +++ b/library/src/solver_if.cpp @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -676,15 +678,15 @@ auto solve_board_internal( } // Print TT stats if requested - if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && std::string(env) == "1") { + if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && env[0] == '1' && env[1] == '\0') { ThreadData* thrp_ptr = ctx.thread_ptr(); if (thrp_ptr && thrp_ptr->tt_lookup_count > 0) { double hit_rate = 100.0 * (double)thrp_ptr->tt_hit_count / (double)thrp_ptr->tt_lookup_count; std::fprintf(stderr, - "DDS_TT_STATS: lookups=%llu hits=%llu hit_rate=%.2f%%\n", - (unsigned long long)thrp_ptr->tt_lookup_count, - (unsigned long long)thrp_ptr->tt_hit_count, + "DDS_TT_STATS: lookups=%" PRIu64 " hits=%" PRIu64 " hit_rate=%.2f%%\n", + thrp_ptr->tt_lookup_count, + thrp_ptr->tt_hit_count, hit_rate); if (auto* tt = ctx.trans_table()) { int adds, overwrites, harvests; @@ -843,15 +845,15 @@ auto solve_same_board( } // Print TT stats if requested - if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && std::string(env) == "1") { + if (auto* env = std::getenv("DDS_PRINT_TT_STATS"); env && env[0] == '1' && env[1] == '\0') { ThreadData* thrp_ptr = ctx.thread_ptr(); if (thrp_ptr && thrp_ptr->tt_lookup_count > 0) { double hit_rate = 100.0 * (double)thrp_ptr->tt_hit_count / (double)thrp_ptr->tt_lookup_count; std::fprintf(stderr, - "DDS_TT_STATS: lookups=%llu hits=%llu hit_rate=%.2f%%\n", - (unsigned long long)thrp_ptr->tt_lookup_count, - (unsigned long long)thrp_ptr->tt_hit_count, + "DDS_TT_STATS: lookups=%" PRIu64 " hits=%" PRIu64 " hit_rate=%.2f%%\n", + thrp_ptr->tt_lookup_count, + thrp_ptr->tt_hit_count, hit_rate); if (auto* tt = ctx.trans_table()) { int adds, overwrites, harvests;