bench: add repeatable solver measurements

Add an opt-in benchmark probe and JSON runner that separate solve, construction, validation, and rendering time. Record stable search counters, environment metadata, warm-up and repetition policy, timeouts, errors, median spread, and instrumentation overhead.

Compile production solving without counters and interleave counted and plain trials when measuring overhead. Keep heavyweight cases outside the default correctness path while testing counter and report behavior cheaply.

Tests: Debug and Release CTest suites (7 passed each)

Refs: #8
This commit is contained in:
Codex instance
2026-07-30 17:08:05 +01:00
parent ce39d0a4d0
commit 598667b2f3
7 changed files with 555 additions and 1 deletions
+47 -1
View File
@@ -228,11 +228,30 @@ namespace {
/** Vector used to identify the available squares. */
using Avail = std::vector<size_t>;
/** Optional search instrumentation.
*
* Counters for search features which are not implemented by the current
* single-threaded solver remain zero. Keeping them in the stable output
* schema lets later solver implementations remain comparable.
*/
struct SearchCounters {
size_t search_nodes = 0;
size_t loop_iterations = 0;
size_t attempted_placements = 0;
size_t backtracks = 0;
size_t prune_checks = 0;
size_t prune_hits = 0;
size_t generated_tasks = 0;
size_t completed_tasks = 0;
};
/** Find a solution to the \a n th Partridge problem.
*
* Returns the grid of the solution.
*/
auto find_solution(size_t const n) noexcept -> Results {
template<bool Instrument>
auto find_solution_impl(size_t const n, SearchCounters *const counters) noexcept
-> Results {
/* Implementation is iterative, as opposed to recursive.
*
* The recursive implementation is easier to understand - but is
@@ -264,7 +283,15 @@ namespace {
Pos pos = 0;
size_t idx = n;
if constexpr (Instrument) {
assert(counters != nullptr);
++counters->search_nodes;
}
while (true) {
if constexpr (Instrument) {
++counters->loop_iterations;
}
/* If the idx is 0 we've looked at all possible square lengths for this
* position, and they've failed. Pop the last square of the stack, remove
* it from the grid and try the next smaller size in the same position.
@@ -277,6 +304,9 @@ namespace {
sqs.pop_back();
grid.clear(sq);
++avail_sqs[sq.length()];
if constexpr (Instrument) {
++counters->backtracks;
}
pos = sq.pos();
idx = sq.length() - 1;
continue;
@@ -292,6 +322,9 @@ namespace {
* set up to look at the next position.
*/
auto const sq = Square(pos, idx);
if constexpr (Instrument) {
++counters->attempted_placements;
}
--avail_sqs[idx];
grid.add(sq);
sqs.push_back(sq);
@@ -301,11 +334,24 @@ namespace {
// Have we reached the end? If so success!
if (pos == grid.end()) { break; }
if constexpr (Instrument) {
++counters->search_nodes;
}
idx = grid.largest_square(pos, n);
}
return {length, sqs};
}
auto find_solution(size_t const n) noexcept -> Results {
return find_solution_impl<false>(n, nullptr);
}
auto find_solution_instrumented(size_t const n,
SearchCounters &counters) noexcept -> Results {
counters = {};
return find_solution_impl<true>(n, &counters);
}
} // anon namespace
#ifndef PARTRIDGE_TESTING