41 using Row = std::pair<const std::string, Value>;
42 struct ResidentProbe {
44 std::uint64_t* comparisons;
46 struct ResidentKeyLess {
47 using is_transparent = void;
48 bool operator()(std::string_view a, std::string_view b)
const noexcept {
return a < b; }
49 bool operator()(
const ResidentProbe& a, std::string_view b)
const noexcept {
53 bool operator()(std::string_view a,
const ResidentProbe& b)
const noexcept {
60 using ResidentKeys = std::map<std::string_view, Offset, ResidentKeyLess>;
62 std::shared_ptr<Row> owner;
63 typename ResidentKeys::iterator
key;
65 using Residents = std::map<Offset, ResidentRow>;
69 using Held = std::map<Offset, std::shared_ptr<Row>>;
89 return resident_.size() * (
sizeof(
typename Residents::value_type) + 4 *
sizeof(
void*)
90 +
sizeof(
typename ResidentKeys::value_type) + 4 *
sizeof(
void*) + 2 *
sizeof(
void*))
91 + held_.size() * (
sizeof(
typename Held::value_type) + 4 *
sizeof(
void*));
94 template<
bool Const,
bool Sorted = false,
bool Insertion = false>
97 using Owner = std::conditional_t<Const, const DiskResidentIdTable, DiskResidentIdTable>;
98 Owner* owner_ =
nullptr;
100 std::shared_ptr<Row> row_;
102 Iterator(Owner* owner, Offset at, std::shared_ptr<Row> row) noexcept
103 : owner_(owner), at_(at), row_(std::move(row)) {}
108 using reference = std::conditional_t<Const, const Row&, Row&>;
109 using pointer = std::conditional_t<Const, const Row*, Row*>;
112 ++owner_->work_.iterated_rows;
116 ++owner_->work_.iterated_rows;
120 ++owner_->work_.iterated_rows;
121 if constexpr (Sorted) {
122 const auto next = owner_->index_.next_sorted(at_);
124 owner_->spill_implicitly();
126 if (at_ != 0) row_ = owner_->load(at_);
127 }
else if constexpr (Insertion) {
130 const auto next = owner_->resident_.upper_bound(at_);
131 at_ = next == owner_->resident_.end() ? 0 : next->first;
132 row_ = at_ == 0 ? nullptr : next->second.owner;
134 auto next = owner_->resident_.lower_bound(at_);
135 if (next == owner_->resident_.begin()) {
141 row_ = next->second.owner;
146 Iterator
operator++(
int) {
auto old = *
this; ++*
this;
return old; }
148 return owner_ == other.owner_ && at_ == other.at_;
150 bool operator!=(
const Iterator& other)
const noexcept {
return !(*
this == other); }
162 template<
bool Const>
class ResidentIterator {
164 using Owner = std::conditional_t<Const, const DiskResidentIdTable, DiskResidentIdTable>;
165 Owner* owner_ =
nullptr;
166 typename Residents::const_iterator at_{};
167 std::shared_ptr<Row> row_;
168 ResidentIterator(Owner* owner,
typename Residents::const_iterator at) noexcept
169 : owner_(owner), at_(at),
170 row_(at == owner->resident_.end() ?
nullptr : at->second.owner) {}
175 using reference = std::conditional_t<Const, const Row&, Row&>;
176 using pointer = std::conditional_t<Const, const Row*, Row*>;
179 ++owner_->work_.resident_rows_visited;
183 ++owner_->work_.resident_rows_visited;
188 row_ = at_ == owner_->resident_.end() ? nullptr : at_->second.owner;
191 ResidentIterator
operator++(
int)
noexcept {
auto old = *
this; ++*
this;
return old; }
192 bool operator==(
const ResidentIterator& other)
const noexcept {
193 return owner_ == other.owner_ && (!owner_ || at_ == other.at_);
195 bool operator!=(
const ResidentIterator& other)
const noexcept {
return !(*
this == other); }
201 template<
bool Const>
class HeldIterator {
203 using Owner = std::conditional_t<Const, const DiskResidentIdTable, DiskResidentIdTable>;
204 Owner* owner_ =
nullptr;
205 typename Held::const_iterator at_{};
207 std::shared_ptr<Row> row_;
208 HeldIterator(Owner* owner,
typename Held::const_iterator at) noexcept
209 : owner_(owner), at_(at),
210 offset_(at == owner->held_.end() ? 0 : at->first),
211 row_(at == owner->held_.end() ?
nullptr : at->second) {}
216 using reference = std::conditional_t<Const, const Row&, Row&>;
217 using pointer = std::conditional_t<Const, const Row*, Row*>;
220 ++owner_->work_.held_rows_visited;
224 ++owner_->work_.held_rows_visited;
229 offset_ = at_ == owner_->held_.end() ? 0 : at_->first;
230 row_ = at_ == owner_->held_.end() ? nullptr : at_->second;
233 HeldIterator
operator++(
int)
noexcept {
auto old = *
this; ++*
this;
return old; }
235 return owner_ == other.owner_ && (!owner_ || at_ == other.at_);
237 bool operator!=(
const HeldIterator& other)
const noexcept {
return !(*
this == other); }
269 index_.set_cache_capacity(capacity);
275 if (
this != &other) {
277 index_ = std::move(other.index_);
278 resident_ = std::move(other.resident_);
279 resident_by_key_ = std::move(other.resident_by_key_);
280 held_ = std::move(other.held_);
281 work_ = std::exchange(other.work_, {});
282 spill_threshold_ = std::exchange(other.spill_threshold_, kSpillFloor);
283 spill_interval_ = std::exchange(other.spill_interval_, kSpillFloor);
284 calls_since_spill_ = std::exchange(other.calls_since_spill_, 0);
285 fixture_spill_interval_ = std::exchange(other.fixture_spill_interval_, 0);
286 fixture_spill_predicate_ = std::exchange(other.fixture_spill_predicate_,
nullptr);
287 other.resident_by_key_.clear();
288 other.resident_.clear();
296 void set_directory(
const std::string& directory) { index_.set_directory(directory); }
298 index_.bind_scratch(slab,
resource);
304 index_.require_insert(
key);
305 if (!index_.bounded_cohorts())
return;
307 if (found ==
end() || found->second.spillable()) require_live_growth(1);
310 index_.require_inserts(std::forward<Enumerate>(enumerate));
311 if (!index_.bounded_cohorts())
return;
312 std::size_t ordinal = 0, extra = 0;
313 enumerate([&](
const std::string&
key) {
314 const auto current = ordinal++;
316 if (found !=
end() && !found->second.spillable())
return;
317 bool repeated =
false;
318 std::size_t prior = 0;
319 enumerate([&](
const std::string& other) {
320 if (prior++ < current && other ==
key) repeated =
true;
327 require_live_growth(extra);
331 resident_by_key_.clear();
335 spill_threshold_ = spill_interval_ = kSpillFloor;
336 calls_since_spill_ = 0;
339 std::uint64_t
size() const noexcept {
return index_.size(); }
341 std::size_t
held_size() const noexcept {
return held_.size(); }
347 return Range<ResidentIterator<false>>{
348 {
this, resident_.begin()}, {
this, resident_.end()}};
351 return Range<ResidentIterator<true>>{
352 {
this, resident_.begin()}, {
this, resident_.end()}};
356 return Range<HeldIterator<false>>{{
this, held_.begin()}, {
this, held_.end()}};
360 return Range<HeldIterator<true>>{{
this, held_.begin()}, {
this, held_.end()}};
365 assert(row.owner_ ==
this && row.row_);
368 template<
bool Const>
void refresh_held(
const HeldIterator<Const>& row)
const {
369 assert(row.owner_ ==
this && row.row_);
375 auto next = held_.begin();
376 for (
const auto& row : resident_) {
377 if (!is_held(row.second.owner->second, 0))
continue;
378 if (next == held_.end() || next->first != row.first
379 || next->second != row.second.owner)
return false;
382 return next == held_.end();
385 std::size_t total = 0;
386 for (
const auto& row : resident_) total += row.second.owner->second.opened.size();
390 const auto at = resident_.begin();
392 at == resident_.end() ? nullptr : at->second.owner};
393 return Range<KeyIterator<insertion_iterator>>{
394 KeyIterator<insertion_iterator>(first),
398 const auto at = index_.first_sorted();
399 return Range<sorted_iterator>{{
this, at, at ? load(at) :
nullptr}, {
this, 0, {}}};
402 const auto at = index_.first_sorted();
403 return Range<const_sorted_iterator>{{
this, at, at ? load(at) :
nullptr}, {
this, 0, {}}};
407 for (
auto at = index_.first_inserted(); at != 0; at = index_.next_inserted(at))
408 visit(index_.key(at));
413 for (
auto at = index_.first_sorted(); at != 0; at = index_.next_sorted(at))
414 visit(index_.key(at));
418 bool contains(
const std::string&
key)
const {
return index_.find(
key).has_value(); }
421 const auto at = index_.find(
key);
422 return at ?
iterator{
this, *at, load(*at)} :
end();
426 const auto at = index_.find(
key);
432 const auto at = resident_by_key_.find(ResidentProbe{
key, &work_.find_resident_probes});
433 if (at == resident_by_key_.end())
return end();
434 const auto row = resident_.find(at->second);
435 return {
this, row->first, row->second.owner};
438 const auto at = resident_by_key_.find(ResidentProbe{
key, &work_.find_resident_probes});
439 if (at == resident_by_key_.end())
return end();
440 const auto row = resident_.find(at->second);
441 return {
this, row->first, row->second.owner};
446 index_.require_insert(
key);
447 std::size_t extra = 0;
448 if (index_.bounded_cohorts() && !
value.spillable() && !index_.find(
key)) {
450 require_live_growth(extra);
452 spill_before_admission(extra);
454 const auto placed = index_.insert(
key,
value.retired_record());
455 if (!placed.second)
return {{
this, placed.first, load(placed.first)},
false};
456 auto row = std::make_shared<Row>(
key, std::move(
value));
457 insert_resident(placed.first, row);
458 return {{
this, placed.first, std::move(row)},
true};
461 for (
auto at = resident_.begin(); at != resident_.end();) {
462 ++work_.rows_visited_by_spill;
463 if (at->second.owner.use_count() == 1 && at->second.owner->second.spillable()
464 && (fixture_spill_predicate_ ==
nullptr
465 || fixture_spill_predicate_(at->second.owner->first))
467 index_.set(at->first, at->second.owner->second.retired_record());
468 ++work_.rows_spilled;
469 assert(held_.find(at->first) == held_.end());
470 resident_by_key_.erase(at->second.key);
471 at = resident_.erase(at);
474 const auto count = resident_.size();
475 const auto maximum = std::numeric_limits<std::size_t>::max();
476 spill_threshold_ = std::max(kSpillFloor, count > maximum / 2 ? maximum : count * 2);
477 spill_interval_ = std::max(kSpillFloor, count);
478 calls_since_spill_ = 0;
485 const auto interval = fixture_spill_interval_ ? fixture_spill_interval_ : spill_interval_;
486 if (++calls_since_spill_ >= interval || resident_.size() >= spill_threshold_)
spill();
491 fixture_spill_interval_ = calls;
496 fixture_spill_predicate_ = predicate;
500 void require_live_growth(std::size_t extra)
const {
509 std::size_t live = 0;
510 for (
const auto& row : resident_) {
512 if (!row.second.owner->second.spillable()) ++live;
518 void spill_before_admission(std::size_t extra)
const {
522 void insert_resident(Offset at,
const std::shared_ptr<Row>& row)
const {
525 const auto key = std::string_view(row->first);
526 const auto indexed = resident_by_key_.emplace(
key, at);
528 resident_.emplace(at, ResidentRow{row, indexed.first});
529 if (is_held(row->second, 0)) held_.emplace(at, row);
532 resident_by_key_.erase(indexed.first);
538 static auto is_held(
const T&
value,
int)
noexcept ->
decltype(
value.held()) {
543 static bool is_held(
const Value&,
long)
noexcept {
return false; }
548 void refresh_held(Offset at,
const std::shared_ptr<Row>& row)
const {
549 const auto found = held_.lower_bound(at);
550 if (is_held(row->second, 0)) {
551 if (found == held_.end() || found->first != at) held_.emplace_hint(found, at, row);
552 }
else if (found != held_.end() && found->first == at) {
559 template<
class T = Value>
560 auto restore_record(
const typename T::Record& record,
int)
const
561 ->
decltype(T::restore_record(record,
bool{})) {
562 return T::restore_record(record, index_.arena_backed());
564 Value restore_record(
const typename Value::Record& record,
long)
const {
565 return Value::restore_record(record);
567 template<
bool Const,
class Owner>
569 const auto at = owner->resident_.rbegin();
570 return {owner, at == owner->resident_.rend() ? 0 : at->first,
571 at == owner->resident_.rend() ? nullptr : at->second.owner};
573 std::shared_ptr<Row> load(Offset at)
const {
574 const auto found = resident_.find(at);
575 if (found != resident_.end())
return found->second.owner;
576 auto value = restore_record(index_.get(at), 0);
577 const std::size_t extra =
value.spillable() ? 0 : 1;
578 require_live_growth(extra);
579 spill_before_admission(extra);
580 auto row = std::make_shared<Row>(index_.key(at), std::move(
value));
581 insert_resident(at, row);
582 ++work_.rows_restored;
585 static constexpr std::size_t kSpillFloor = 64;
586 mutable Index index_;
587 mutable Residents resident_;
588 mutable ResidentKeys resident_by_key_;
591 mutable std::size_t spill_threshold_ = kSpillFloor, spill_interval_ = kSpillFloor;
592 mutable std::size_t calls_since_spill_ = 0;
593 std::size_t fixture_spill_interval_ = 0;
594 bool (*fixture_spill_predicate_)(
const std::string&) =
nullptr;
bool bounded_cohorts() const noexcept
bool operator==(const HeldIterator &other) const noexcept
std::conditional_t< Const, const Row *, Row * > pointer
std::conditional_t< Const, const Row &, Row & > reference
friend class DiskResidentIdTable
std::ptrdiff_t difference_type
bool operator!=(const HeldIterator &other) const noexcept
pointer operator->() const noexcept
HeldIterator operator++(int) noexcept
reference operator*() const noexcept
HeldIterator & operator++() noexcept
std::forward_iterator_tag iterator_category
std::conditional_t< Const, const Row *, Row * > pointer
std::forward_iterator_tag iterator_category
friend class DiskResidentIdTable
reference operator*() const noexcept
bool operator==(const Iterator &other) const noexcept
bool operator!=(const Iterator &other) const noexcept
std::ptrdiff_t difference_type
pointer operator->() const noexcept
std::conditional_t< Const, const Row &, Row & > reference
bool operator==(const KeyIterator &other) const noexcept
KeyIterator & operator++()
bool operator!=(const KeyIterator &other) const noexcept
std::forward_iterator_tag iterator_category
std::ptrdiff_t difference_type
const std::string * pointer
pointer operator->() const noexcept
KeyIterator operator++(int)
reference operator*() const noexcept
const std::string & reference
ResidentIterator()=default
pointer operator->() const noexcept
reference operator*() const noexcept
bool operator==(const ResidentIterator &other) const noexcept
std::forward_iterator_tag iterator_category
friend class DiskResidentIdTable
ResidentIterator & operator++() noexcept
ResidentIterator operator++(int) noexcept
std::conditional_t< Const, const Row &, Row & > reference
std::ptrdiff_t difference_type
std::conditional_t< Const, const Row *, Row * > pointer
bool operator!=(const ResidentIterator &other) const noexcept
std::uint64_t size() const noexcept
std::size_t resident_opened_slot_count() const noexcept
Iterator< false > iterator
auto resident_rows() const noexcept
void require_inserts(Enumerate &&enumerate) const
void set_store(ScratchStore store) noexcept
const_iterator find_resident(const std::string &key) const noexcept
std::size_t resident_size() const noexcept
DiskResidentIdTable(std::size_t cache_bytes)
DiskResidentIdTable(CacheCapacity capacity)
void require_insert(const std::string &key) const
void for_each_id(F &&visit) const
Iterator< true > const_iterator
void for_each_sorted_id(F &&visit) const
void fixture_implicit_spill_interval(std::size_t calls) noexcept
DiskResidentIdTable & operator=(DiskResidentIdTable &&other) noexcept
auto resident_rows() noexcept
const_iterator find(const std::string &key) const
auto resident_ids() const noexcept
bool held_rows_exact() const noexcept
iterator find(const std::string &key)
void refresh_held(const iterator &row) const
void spill_implicitly() const
std::pair< iterator, bool > emplace(const std::string &key, Value value)
void refresh_held(const HeldIterator< Const > &row) const
DiskResidentIdTable & operator=(const DiskResidentIdTable &)=delete
std::size_t held_size() const noexcept
iterator find_resident(const std::string &key) noexcept
DiskResidentIdTable()=default
const_iterator end() const noexcept
void bind_scratch(const std::shared_ptr< ScratchSlab > &slab, ScratchResource resource)
bool contains(const std::string &key) const
DiskResidentIdTable(IdTableReadCache::Policy cache)
DiskResidentIdTable(DiskResidentIdTable &&other) noexcept
const WorkCounters & work_counters() const noexcept
void set_cache_capacity(CacheCapacity capacity)
static constexpr std::size_t resident_row_bytes() noexcept
auto held_rows() noexcept
void set_directory(const std::string &directory)
Iterator< true, true > const_sorted_iterator
DiskResidentIdTable(const DiskResidentIdTable &)=delete
std::size_t resident_directory_heap_estimate() const noexcept
Iterator< false, true > sorted_iterator
const_iterator begin() const noexcept
auto held_rows() const noexcept
iterator begin() noexcept
void fixture_spill_predicate(bool(*predicate)(const std::string &)) noexcept
Iterator< true, false, true > insertion_iterator
constexpr std::size_t kMaxResidentCohorts
std::string scratch_resident_cohort_limit_message(std::uint64_t limit)
std::uint64_t iterated_rows
std::uint64_t find_resident_probes
std::uint64_t rows_visited_by_spill
std::uint64_t rows_visited_by_capacity
std::uint64_t held_rows_visited
std::uint64_t rows_restored
std::uint64_t resident_rows_visited
std::uint64_t rows_spilled