11#include <jaffarCommon/concurrent.hpp>
12#include <jaffarCommon/hash.hpp>
13#include <jaffarCommon/json.hpp>
14#include <jaffarCommon/logger.hpp>
15#include <jaffarCommon/parallel.hpp>
43 _maxStoreCount = jaffarCommon::json::getNumber<size_t>(config,
"Max Store Count");
44 _maxStoreSizeMb = jaffarCommon::json::getNumber<double>(config,
"Max Store Size (Mb)");
47 if (
auto* value = std::getenv(
"JAFFAR_ENGINE_OVERRIDE_MAX_HASHDB_SIZE_MB"))
_maxStoreSizeMb = std::stoul(value);
57 for (
auto* s :
_l1)
delete s;
58 if (
_l2 !=
nullptr)
delete _l2;
75 const size_t totalBudgetBytes = (size_t)(
_maxStoreSizeMb * 1024.0 * 1024.0);
87 _l1.resize(_numaCount);
88 for (
int i = 0; i < _numaCount; i++)
_l1[i] =
makeStore();
111 constexpr double toMb = 1.0 / (1024.0 * 1024.0);
118 return (
double)bytes * toMb;
125 auto printStores = [
this, toMb, &storeSizeMb](
const NUMAStore_t* s,
const char* label)
128 for (
auto it = s->_hashStores.rbegin(); it != s->_hashStores.rend(); ++it, ++idx)
130 const bool cur = (it == s->_hashStores.rbegin());
131 const size_t q = cur ? s->getQueryCount() : it->queryCount;
132 const size_t c = cur ? s->getCollisionCount() : it->collisionCount;
133 jaffarCommon::logger::log(
"[J+] + %sStore %lu/%lu - Id: %lu - Age: %lu, Entries: %.3f M, Size: %.1f Mb, Check Count: %lu, Collision Count: %lu (Rate %.3f%%)\n", label,
135 q == 0 ? 0.0 : 100.0 * (double)c / (
double)q);
143 printStores(
_l2,
"");
148 size_t l1q = 0, l1c = 0, l1entries = 0;
149 double l1SizeMb = 0.0;
155 for (
auto*
const g :
_l1)
157 l1q += g->getTotalQueryCount();
158 l1c += g->getTotalCollisionCount();
159 for (
const auto& s : g->_hashStores) l1entries += s.hashSet.size();
160 l1SizeMb += storeSizeMb(g);
164 size_t l2entries = 0;
165 for (
const auto& s :
_l2->
_hashStores) l2entries += s.hashSet.size();
173 jaffarCommon::logger::log(
"[J+] Two-Tier Dedup (%d NUMA-local L1 + global L2):\n", _numaCount);
174 jaffarCommon::logger::log(
"[J+] + L1 (local): Checks: %lu, Local Hits: %lu (%.3f%%), Entries: %.3f M, Size: %.1f / %.1f Mb\n", l1q, l1c,
175 l1q == 0 ? 0.0 : 100.0 * (double)l1c / (
double)l1q, (double)l1entries * toMb, l1SizeMb, l1BudgetMb);
176 jaffarCommon::logger::log(
177 "[J+] + L2 (global): Checks (L1 misses): %lu (%.3f%% of all), Cross-Domain Hits: %lu, Entries: %.3f M, Size: %.1f / %.1f Mb\n", l2q,
178 l1q == 0 ? 0.0 : 100.0 * (double)l2q / (
double)l1q, l2c, (double)l2entries * toMb, storeSizeMb(
_l2), l2BudgetMb);
179 jaffarCommon::logger::log(
"[J+] + Served locally (no L2 access): %.3f%% | Total dup rate: %.3f%%\n", l1q == 0 ? 0.0 : 100.0 * (double)l1c / (
double)l1q,
180 l1q == 0 ? 0.0 : 100.0 * (double)(l1c + l2c) / (double)l1q);
182 printStores(
_l2,
"L2 ");
197 const auto threadId = jaffarCommon::parallel::getThreadId();
211 auto*
const l1 =
_l1[_preferredNumaDomain];
212 l1->_threadQueryCount[threadId].value++;
215 l1->_threadCollisionCount[threadId].value++;
236 __INLINE__
void insertHash(
const jaffarCommon::hash::hash_t hash)
238 if (
_l1.empty() ==
false)
_l1[_preferredNumaDomain]->_hashStores.rbegin()->hashSet.insert(hash);
273 jaffarCommon::concurrent::HashSet_t<jaffarCommon::hash::hash_t>
hashSet = {};
364 s->
_hashStores.push_back(
hashStore_t({.id = s->_currentHashStoreId++, .age = s->_currentAge, .queryCount = 0, .collisionCount = 0}));
365 s->_threadQueryCount.resize(_threadCount);
366 s->_threadCollisionCount.resize(_threadCount);
383 size_t curHashStoreIdx = 0;
386 bool collisionFound =
false;
387 if (curHashStoreIdx == 0)
388 collisionFound = itr->hashSet.insert(hash).second ==
false;
390 collisionFound = itr->hashSet.contains(hash);
391 if (collisionFound ==
true)
return true;
453 static constexpr size_t _bytesPerSlot =
sizeof(jaffarCommon::hash::hash_t) +
sizeof(uint8_t);
471 std::vector<NUMAStore_t*>
_l1;
Two-tier hash database that deduplicates visited states across the whole search.
void printInfo() const
Logs the database's current state, including per-generation breakdowns of the rolling stores and aggr...
void advanceStep()
Signals that a new search step has started, rolling and ageing every store.
~HashDb()
Destroys the database, deleting all per-domain L1 stores and the global L2 store.
size_t getStoreSizeBytes(const hashStore_t &store) const
Computes the actual resident memory of a hash store generation from the underlying set's measured all...
std::vector< NUMAStore_t * > _l1
Per-domain NUMA-local L1 caches. Empty on a single-domain run.
bool probeStore(NUMAStore_t *const store, const jaffarCommon::hash::hash_t hash)
Reverse-scans a store's hash sets (newest first), inserting into the current set and checking-only th...
NUMAStore_t * makeStore()
Creates a fresh store with a single (empty) hash set generation and per-thread counters.
size_t _maxStoreSizeBytes
Maximum global L2 store size, in bytes (derived from _maxStoreSizeMb).
bool checkHashExists(const jaffarCommon::hash::hash_t hash)
Checks whether the given hash is already present in the database, inserting it if new.
size_t _l1MaxStoreSizeBytes
Per-domain L1 store budget, in bytes (derived: global budget / number of NUMA domains).
void initialize()
Allocates the hash stores and splits the configured budget across the L1 and L2 tiers.
size_t getMaxBudgetBytes() const
Configured PEAK hash-DB footprint in bytes = Max Store Size x Max Store Count (the rolling generation...
size_t _maxStoreCount
Maximum number of store generations to keep at any time.
static constexpr size_t _bytesPerSlot
Per-slot memory cost of the underlying phmap flat hash set.
void rollStore(NUMAStore_t *const g, const size_t maxBytes)
Rolls a single store and advances its age.
static constexpr size_t _hashStoreFixedOverhead
Fixed overhead of a hash store, independent of the number of entries.
HashDb(const nlohmann::json &config)
Constructs the hash database from its configuration block.
double _maxStoreSizeMb
Maximum (global L2) store size, in megabytes.
NUMAStore_t * _l2
The single global authoritative store (holds the complete dedup set).
void insertHash(const jaffarCommon::hash::hash_t hash)
Inserts a hash into the current (newest) store(s) without checking for collisions.
NUMA topology detection: distance/preference matrices and per-domain delegate-thread selection,...
A single (L1 or L2) hash store: a rolling window of hashStore_t generations together with its rolling...
std::vector< paddedCounter_t > _threadQueryCount
Per-thread query counts for the current store, indexed by thread id.
size_t getTotalQueryCount() const
Lifetime (since-start) totals = already-rolled generations + the current window.
size_t _lifetimeCollisionCount
Lifetime collision total; companion to _lifetimeQueryCount (same since-start window).
void resetCounts()
Resets all per-thread query and collision counters to zero.
size_t getTotalCollisionCount() const
Lifetime (since-start) collision total = already-rolled generations + the current window (the collisi...
size_t _currentAge
Number of steps elapsed since this store began, assigned as the age of newly created generations.
std::deque< hashStore_t > _hashStores
The rolling window of hash store generations (oldest at front, current/newest at back).
size_t _currentHashStoreId
Next id to assign to a newly created hash store generation.
size_t _lifetimeQueryCount
Lifetime query/collision totals from all ALREADY-ROLLED generations (the per-thread counters above ar...
std::vector< paddedCounter_t > _threadCollisionCount
Per-thread collision counts for the current store, indexed by thread id.
size_t getCollisionCount() const
Sums the per-thread collision counts.
size_t getQueryCount() const
Sums the per-thread query counts.
One generation of a rolling store: a hash set of previously found states plus its identity,...
size_t queryCount
How many queries were made (snapshot of summed per-thread counts at roll time).
const size_t id
The store id.
size_t collisionCount
How many collisions were detected (snapshot of summed per-thread counts at roll time).
jaffarCommon::concurrent::HashSet_t< jaffarCommon::hash::hash_t > hashSet
The internal set of hashes for this store generation.
const size_t age
The store age (number of steps elapsed since it was created).
A query/collision counter padded to a full cache line to avoid false sharing.
size_t value
The counter's accumulated value.