RotationIndexer: keep 1024 indexing outcomes, keyed by a digest of the input

A run on data that index poorly asks over a hundred indexing questions - every rung of the
spot-budget ladder, in every pass and walk probe - and a later pass repeats a probe's ladder, which
the 32-entry memo had long evicted (about 20 s on one such sweep). The key holds every spot, so it is
now kept as two independent 64-bit hashes and its length instead of whole.
RUGNUX_VERIFY_FIRST_PASS_MEMO still recomputes and compares.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C
This commit is contained in:
2026-09-26 22:46:20 +02:00
co-authored by Claude Opus 5.5
parent 92877e5eb7
commit 79bc90e512
@@ -13,6 +13,7 @@
#include <future>
#include <mutex>
#include <stdexcept>
#include <string_view>
namespace {
// Sub-cell override thresholds used in candidate selection to undo a spurious axis doubling:
@@ -160,16 +161,32 @@ namespace {
static_cast<double>(x.GetIndexingThreads()), static_cast<double>(x.GetRefineThreads())});
}
// The input key is kept as two independent 64-bit hashes of its bytes plus its length - a key
// holds every spot, so keeping it whole would cost megabytes per entry.
struct KeyDigest {
size_t n;
uint64_t fnv, std_hash;
bool operator==(const KeyDigest &) const = default;
};
KeyDigest Digest(const std::vector<double> &key) {
const std::string_view bytes(reinterpret_cast<const char *>(key.data()), key.size() * sizeof(double));
uint64_t fnv = 14695981039346656037ULL; // FNV-1a
for (const char c : bytes)
fnv = (fnv ^ static_cast<uint8_t>(c)) * 1099511628211ULL;
return {key.size(), fnv, std::hash<std::string_view>{}(bytes)};
}
struct IndexingMemo {
std::mutex m;
std::vector<std::pair<std::vector<double>, RotationIndexer::IndexingOutcome>> entries;
std::vector<std::pair<KeyDigest, RotationIndexer::IndexingOutcome>> entries;
};
IndexingMemo &Memo() {
static IndexingMemo memo;
return memo;
}
// A run asks a handful of distinct questions at most; the oldest go first.
constexpr size_t MAX_INDEXING_MEMOS = 32;
// A run on data that index poorly asks well over a hundred questions (every rung of the spot-budget
// ladder, in every pass and probe), and a later pass repeats a probe's; the oldest go first.
constexpr size_t MAX_INDEXING_MEMOS = 1024;
// Bitwise comparison of what a recomputation gave against what was kept.
bool SameOutcome(const RotationIndexer::IndexingOutcome &a, const RotationIndexer::IndexingOutcome &b) {
@@ -229,7 +246,7 @@ void RotationIndexer::RunIndexing() {
if (!axis_)
return;
const std::vector<double> key = InputKey();
const KeyDigest key = Digest(InputKey());
std::optional<IndexingOutcome> kept;
{
auto &memo = Memo();