return make_pair(bp, ep);
}
+void
+BeesHashTable::recompute_extent_fill_histogram_locked(uint64_t extent_index)
+{
+ // Caller holds the extent lock (or runs single-threaded at startup).
+ // Rebuild this extent's per-bucket occupancy histogram from the live
+ // cells. Cheap (c_buckets_per_extent buckets) and it rides on work the
+ // caller already does under the lock — the startup load and each
+ // writeback — so the periodic survey never has to walk cells itself.
+ auto &hist = m_extent_metadata.at(extent_index).m_fill_histogram;
+ hist.fill(0);
+ for (Bucket *bucket = m_extent_ptr[extent_index].p_buckets; bucket < m_extent_ptr[extent_index + 1].p_buckets; ++bucket) {
+ size_t occupied = 0;
+ for (Cell *cell = bucket[0].p_cells; cell < bucket[1].p_cells; ++cell) {
+ if (cell->e_addr) {
+ ++occupied;
+ }
+ }
+ ++hist.at(occupied);
+ }
+}
+
bool
BeesHashTable::flush_dirty_extent(uint64_t extent_index)
{
auto lock = lock_extent_by_index(extent_index);
bool wrote_extent = false;
+ // Refresh the occupancy fragment while we hold the lock and the extent
+ // is about to be persisted; the survey sums these instead of walking
+ // cells.
+ recompute_extent_fill_histogram_locked(extent_index);
+
if (!m_persistent) {
// Memory-only mode: mark clean without any file I/O.
m_extent_metadata.at(extent_index).m_dirty = false;
BEESLOGDEBUG("Exited hash table writeback_loop");
}
-static
-string
-percent(size_t num, size_t den)
-{
- if (den) {
- return astringprintf("%u%%", num * 100 / den);
- } else {
- return "--%";
- }
-}
-
void
BeesHashTable::prefetch_loop()
{
size_t width = 64;
vector<size_t> occupancy(width, 0);
size_t occupied_count = 0;
- size_t total_count = 0;
- size_t compressed_count = 0;
- size_t compressed_offset_count = 0;
- size_t toxic_count = 0;
- size_t unaligned_eof_count = 0;
+ const size_t total_count = m_cells;
+ // Sum the per-extent occupancy fragments maintained at load and
+ // writeback — no cell walk. Each extent contributes its cached
+ // bucket-fill histogram. The old type breakdowns (compressed,
+ // unaligned_eof, toxic) are gone: those were v1 BeesAddress bits
+ // that scan-next neither sets on store nor reads on fetch.
for (uint64_t ext = 0; ext < m_extents && !m_stop_requested; ++ext) {
catch_all([&]() {
- BEESNOTE("analyzing hash table extent #" << ext << " of " << m_extents);
- bool duplicate_bugs_found = false;
+ BEESNOTE("summarizing hash table extent #" << ext << " of " << m_extents);
auto lock = lock_extent_by_index(ext);
- for (Bucket *bucket = m_extent_ptr[ext].p_buckets; bucket < m_extent_ptr[ext + 1].p_buckets; ++bucket) {
- if (verify_cell_range(bucket[0].p_cells, bucket[1].p_cells)) {
- duplicate_bugs_found = true;
- }
- size_t this_bucket_occupied_count = 0;
- for (Cell *cell = bucket[0].p_cells; cell < bucket[1].p_cells; ++cell) {
- if (cell->e_addr) {
- ++this_bucket_occupied_count;
- BeesAddress a(cell->e_addr);
- if (a.is_compressed()) {
- ++compressed_count;
- if (a.has_compressed_offset()) {
- ++compressed_offset_count;
- }
- }
- if (a.is_toxic()) {
- ++toxic_count;
- }
- if (a.is_unaligned_eof()) {
- ++unaligned_eof_count;
- }
- }
- ++total_count;
- }
- ++occupancy.at(this_bucket_occupied_count * width / (1 + c_cells_per_bucket) );
- // Count these instead of calculating the number so we get better stats in case of exceptions
- occupied_count += this_bucket_occupied_count;
- }
- if (duplicate_bugs_found) {
- set_extent_dirty_locked(ext);
+ const auto &hist = m_extent_metadata.at(ext).m_fill_histogram;
+ for (size_t fill = 0; fill < hist.size(); ++fill) {
+ const size_t buckets_at_fill = hist[fill];
+ occupancy.at(fill * width / (1 + c_cells_per_bucket)) += buckets_at_fill;
+ occupied_count += fill * buckets_at_fill;
}
});
}
out << "\n";
}
- size_t uncompressed_count = occupied_count - compressed_offset_count;
-
ostringstream graph_blob;
graph_blob << "Now: " << format_time(time(NULL)) << "\n";
graph_blob
<< "\nHash table page occupancy histogram (" << occupied_count << "/" << total_count << " cells occupied, " << (occupied_count * 100 / total_count) << "%)\n"
- << out.str() << "0% | 25% | 50% | 75% | 100% page fill\n"
- << "compressed " << compressed_count << " (" << percent(compressed_count, occupied_count) << ")\n"
- << "uncompressed " << uncompressed_count << " (" << percent(uncompressed_count, occupied_count) << ")"
- << " unaligned_eof " << unaligned_eof_count << " (" << percent(unaligned_eof_count, occupied_count) << ")"
- << " toxic " << toxic_count << " (" << percent(toxic_count, occupied_count) << ")";
+ << out.str() << "0% | 25% | 50% | 75% | 100% page fill";
graph_blob << "\n\n";
// faster to steady state and lets the lookup/insert paths assume the
// table is always resident. Memory-only mode skips this —
// the pages are already zero from MAP_ANONYMOUS.
- if (m_persistent) {
- for (uint64_t ext = 0; ext < m_extents; ++ext) {
+ for (uint64_t ext = 0; ext < m_extents; ++ext) {
+ if (m_persistent) {
BEESNOTE("loading hash extent #" << ext << " of " << m_extents);
uint8_t *const extent_begin = m_extent_ptr[ext ].p_byte;
uint8_t *const extent_end = m_extent_ptr[ext + 1].p_byte;
pread_or_die(m_fd, extent_begin, extent_end - extent_begin, extent_begin - m_byte_ptr);
BEESCOUNT(hash_extent_in);
}
+ // Seed the occupancy fragment from the just-loaded (or, in
+ // memory-only mode, zeroed) extent so the survey has data before
+ // the first writeback.
+ recompute_extent_fill_histogram_locked(ext);
}
// Pre-fault and lock now, while we know which size the host has to