Files
UniversalContainer/include/universal_container/adaptive_sequence.hpp
T
Efim Beshmenev aea6e330e3 Realisation v1
2026-08-11 22:19:34 +03:00

1039 lines
37 KiB
C++

#pragma once
#include "universal_container/adaptation_policy.hpp"
#include "universal_container/flat_hash_index.hpp"
#include "universal_container/tiered_storage.hpp"
#include <algorithm>
#include <cassert>
#include <compare>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iterator>
#include <limits>
#include <optional>
#include <span>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
namespace uc {
template <class T,
bool HashIndexEnabled = false,
class AdaptationPolicy = CostModelPolicy,
class Hash = std::hash<T>,
class Equal = std::equal_to<T>>
class AdaptiveSequence {
public:
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using stable_id = std::uint64_t;
static constexpr bool hash_index_enabled = HashIndexEnabled;
static constexpr stable_id invalid_id = std::numeric_limits<stable_id>::max();
private:
using internal_id = typename detail::FlatDuplicateIndex<T, Hash, Equal>::id_type;
static constexpr internal_id invalid_internal_id =
detail::FlatDuplicateIndex<T, Hash, Equal>::invalid_id;
struct IndexedRecord {
T value;
internal_id id = invalid_internal_id;
};
using record_type = std::conditional_t<HashIndexEnabled, IndexedRecord, T>;
using vector_storage = std::vector<record_type>;
using tiered_storage = detail::TieredStorage<record_type>;
struct IdMetadata {
internal_id previous = invalid_internal_id;
internal_id next = invalid_internal_id;
std::uint32_t primary = 0; // vector index or stable leaf id
std::uint32_t secondary = 0; // local leaf index
std::uint8_t flags = 0;
};
static constexpr std::uint8_t alive_flag = 0x01;
static constexpr std::uint8_t tiered_flag = 0x02;
struct IndexedState {
detail::FlatDuplicateIndex<T, Hash, Equal> values;
std::vector<IdMetadata> ids;
};
struct NoIndexState {};
using index_state_type = std::conditional_t<HashIndexEnabled, IndexedState, NoIndexState>;
public:
class reference_proxy {
public:
reference_proxy(AdaptiveSequence& owner, size_type index) noexcept
: owner_(&owner), index_(index), generation_(owner.generation_) {}
reference_proxy& operator=(const T& value) {
validate();
owner_->set(index_, value);
return *this;
}
reference_proxy& operator=(T&& value) {
validate();
owner_->set(index_, std::move(value));
return *this;
}
reference_proxy& operator=(const reference_proxy& other) {
return *this = static_cast<const T&>(other);
}
operator const T&() const {
validate();
return owner_->value_at_unchecked(index_);
}
const T* operator->() const {
validate();
return &owner_->value_at_unchecked(index_);
}
const T& get() const {
validate();
return owner_->value_at_unchecked(index_);
}
private:
void validate() const {
if (generation_ != owner_->generation_) {
throw std::logic_error("AdaptiveSequence reference proxy was invalidated");
}
}
AdaptiveSequence* owner_;
size_type index_;
std::uint64_t generation_;
};
using reference = std::conditional_t<HashIndexEnabled, reference_proxy, T&>;
using const_reference = const T&;
template <bool Const>
class basic_iterator {
friend class AdaptiveSequence;
template <bool>
friend class basic_iterator;
using owner_type = std::conditional_t<Const, const AdaptiveSequence, AdaptiveSequence>;
basic_iterator(owner_type* owner, size_type index) noexcept
: owner_(owner), index_(index), generation_(owner ? owner->generation_ : 0) {}
void validate() const {
if (owner_ && generation_ != owner_->generation_) {
throw std::logic_error("AdaptiveSequence iterator was invalidated");
}
}
public:
using iterator_category = std::random_access_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using reference = std::conditional_t<Const, const T&,
typename AdaptiveSequence::reference>;
using pointer = const T*;
basic_iterator() = default;
template <bool OtherConst>
requires(Const && !OtherConst)
basic_iterator(const basic_iterator<OtherConst>& other) noexcept
: owner_(other.owner_), index_(other.index_), generation_(other.generation_) {}
reference operator*() const {
validate();
if constexpr (Const) {
return owner_->value_at_unchecked(index_);
} else if constexpr (HashIndexEnabled) {
return reference(*owner_, index_);
} else {
return owner_->value_at_unchecked(index_);
}
}
pointer operator->() const {
validate();
return &owner_->value_at_unchecked(index_);
}
reference operator[](difference_type offset) const { return *(*this + offset); }
basic_iterator& operator++() noexcept { ++index_; return *this; }
basic_iterator operator++(int) noexcept { auto copy = *this; ++*this; return copy; }
basic_iterator& operator--() noexcept { --index_; return *this; }
basic_iterator operator--(int) noexcept { auto copy = *this; --*this; return copy; }
basic_iterator& operator+=(difference_type offset) noexcept {
index_ = static_cast<size_type>(static_cast<difference_type>(index_) + offset);
return *this;
}
basic_iterator& operator-=(difference_type offset) noexcept { return *this += -offset; }
friend basic_iterator operator+(basic_iterator iterator, difference_type offset) noexcept {
iterator += offset;
return iterator;
}
friend basic_iterator operator+(difference_type offset, basic_iterator iterator) noexcept {
iterator += offset;
return iterator;
}
friend basic_iterator operator-(basic_iterator iterator, difference_type offset) noexcept {
iterator -= offset;
return iterator;
}
friend difference_type operator-(const basic_iterator& left,
const basic_iterator& right) noexcept {
return static_cast<difference_type>(left.index_)
- static_cast<difference_type>(right.index_);
}
friend bool operator==(const basic_iterator&, const basic_iterator&) = default;
friend auto operator<=>(const basic_iterator& left,
const basic_iterator& right) noexcept {
return left.index_ <=> right.index_;
}
private:
owner_type* owner_ = nullptr;
size_type index_ = 0;
std::uint64_t generation_ = 0;
};
using iterator = basic_iterator<false>;
using const_iterator = basic_iterator<true>;
explicit AdaptiveSequence(TieredConfig tiered_config = {},
AdaptationPolicy policy = AdaptationPolicy{})
: tiered_config_(tiered_config), policy_(std::move(policy)), storage_(vector_storage{}) {
if constexpr (requires(AdaptationPolicy& p) { p.set_tiered_config(tiered_config_); }) {
policy_.set_tiered_config(tiered_config_);
}
read_sample_rate_ = configured_read_sample_rate();
read_sample_countdown_ = read_sample_rate_;
edit_sample_rate_ = configured_edit_sample_rate();
edit_sample_countdown_ = edit_sample_rate_;
}
template <std::input_iterator InputIt, std::sentinel_for<InputIt> Sentinel>
AdaptiveSequence(InputIt first,
Sentinel last,
TieredConfig tiered_config = {},
AdaptationPolicy policy = AdaptationPolicy{})
: AdaptiveSequence(tiered_config, std::move(policy)) {
for (; first != last; ++first) {
push_back(*first);
}
}
AdaptiveSequence(const AdaptiveSequence&) = default;
AdaptiveSequence(AdaptiveSequence&&) noexcept = default;
AdaptiveSequence& operator=(const AdaptiveSequence&) = default;
AdaptiveSequence& operator=(AdaptiveSequence&&) noexcept = default;
~AdaptiveSequence() = default;
[[nodiscard]] size_type size() const noexcept {
return mode() == StorageMode::vector
? std::get<vector_storage>(storage_).size()
: std::get<tiered_storage>(storage_).size();
}
[[nodiscard]] bool empty() const noexcept { return size() == 0; }
[[nodiscard]] StorageMode mode() const noexcept {
return std::holds_alternative<vector_storage>(storage_)
? StorageMode::vector
: StorageMode::tiered;
}
[[nodiscard]] ResidencyMode residency_mode() const noexcept { return residency_; }
[[nodiscard]] const TieredConfig& tiered_config() const noexcept { return tiered_config_; }
reference operator[](size_type index) {
if (read_adaptation_ == ReadAdaptationMode::eager_nonconst) {
apply_pending_adaptation();
}
observe_random_read();
if (read_adaptation_ == ReadAdaptationMode::eager_nonconst) {
apply_pending_adaptation();
}
if constexpr (HashIndexEnabled) {
return reference_proxy(*this, index);
} else {
return value_at_unchecked(index);
}
}
const_reference operator[](size_type index) const noexcept {
observe_random_read();
return value_at_unchecked(index);
}
reference at(size_type index) {
check_index(index);
return (*this)[index];
}
const_reference at(size_type index) const {
check_index(index);
return (*this)[index];
}
void push_back(const T& value) { push_back_impl(T(value)); }
void push_back(T&& value) { push_back_impl(std::move(value)); }
void insert(size_type index, const T& value) { insert_impl(index, T(value)); }
void insert(size_type index, T&& value) { insert_impl(index, std::move(value)); }
void erase(size_type index) {
check_index(index);
apply_pending_adaptation();
const auto old_size = size();
const auto id = record_id(record_at_unchecked(index));
if constexpr (HashIndexEnabled) {
unlink_value(value_at_unchecked(index), id);
}
auto relocate = relocation_callback();
if (mode() == StorageMode::vector) {
auto& values = std::get<vector_storage>(storage_);
values.erase(values.begin() + static_cast<difference_type>(index));
refresh_vector_locations(index);
} else {
(void)std::get<tiered_storage>(storage_).erase(index, relocate);
}
if constexpr (HashIndexEnabled) {
auto& metadata = index_state_.ids[static_cast<size_type>(id)];
metadata.flags &= static_cast<std::uint8_t>(~alive_flag);
metadata.previous = invalid_internal_id;
metadata.next = invalid_internal_id;
}
++generation_;
observe_structural_edit(OperationKind::erase, old_size, index);
}
void set(size_type index, const T& value) { set_impl(index, T(value)); }
void set(size_type index, T&& value) { set_impl(index, std::move(value)); }
void clear() {
if constexpr (HashIndexEnabled) {
for (auto& metadata : index_state_.ids) {
metadata.flags &= static_cast<std::uint8_t>(~alive_flag);
metadata.previous = invalid_internal_id;
metadata.next = invalid_internal_id;
}
index_state_.values.clear();
}
storage_.template emplace<vector_storage>();
residency_ = ResidencyMode::automatic;
policy_.reset();
read_sample_countdown_ = read_sample_rate_;
edit_sample_countdown_ = edit_sample_rate_;
++generation_;
}
void reserve(size_type capacity) {
force_vector_mode();
std::get<vector_storage>(storage_).reserve(capacity);
if constexpr (HashIndexEnabled) {
const auto additional = capacity > size() ? capacity - size() : 0;
index_state_.ids.reserve(index_state_.ids.size() + additional);
}
++generation_;
}
[[nodiscard]] bool contains(const T& value) const {
if constexpr (HashIndexEnabled) {
return index_state_.values.find(value) != nullptr;
} else {
return find_one(value).has_value();
}
}
[[nodiscard]] std::optional<size_type> find_one(const T& value) const {
if constexpr (HashIndexEnabled) {
const auto* entry = index_state_.values.find(value);
if (!entry || entry->head == invalid_internal_id) {
return std::nullopt;
}
return resolve_id(entry->head);
} else {
for (size_type i = 0; i < size(); ++i) {
if (equal_(value_at_unchecked(i), value)) {
return i;
}
}
return std::nullopt;
}
}
[[nodiscard]] std::vector<size_type> find_all(const T& value) const {
std::vector<size_type> result;
if constexpr (HashIndexEnabled) {
const auto* entry = index_state_.values.find(value);
if (!entry) {
return result;
}
result.reserve(entry->count);
auto id = entry->head;
while (id != invalid_internal_id) {
result.push_back(resolve_id(id));
id = index_state_.ids[static_cast<size_type>(id)].next;
}
std::sort(result.begin(), result.end());
} else {
for (size_type i = 0; i < size(); ++i) {
if (equal_(value_at_unchecked(i), value)) {
result.push_back(i);
}
}
}
return result;
}
// Fast unordered duplicate lookup. Returning IDs avoids the O(k log k)
// logical-order reconstruction and sort required by find_all().
[[nodiscard]] std::vector<stable_id> find_all_ids(const T& value) const
requires(HashIndexEnabled) {
std::vector<stable_id> result;
const auto* entry = index_state_.values.find(value);
if (!entry) {
return result;
}
result.reserve(entry->count);
auto id = entry->head;
while (id != invalid_internal_id) {
result.push_back(static_cast<stable_id>(id));
id = index_state_.ids[static_cast<size_type>(id)].next;
}
return result;
}
bool erase_one(const T& value) {
const auto found = find_one(value);
if (!found) {
return false;
}
erase(*found);
return true;
}
size_type erase_all(const T& value) {
if constexpr (HashIndexEnabled) {
const auto* entry = index_state_.values.find(value);
if (!entry) {
return 0;
}
std::vector<internal_id> ids;
ids.reserve(entry->count);
auto id = entry->head;
while (id != invalid_internal_id) {
ids.push_back(id);
id = index_state_.ids[static_cast<size_type>(id)].next;
}
for (const auto current : ids) {
erase_by_id(current);
}
return ids.size();
} else {
size_type removed = 0;
for (size_type i = 0; i < size();) {
if (equal_(value_at_unchecked(i), value)) {
erase(i);
++removed;
} else {
++i;
}
}
return removed;
}
}
[[nodiscard]] stable_id id_at(size_type index) const
requires(HashIndexEnabled) {
check_index(index);
return static_cast<stable_id>(record_at_unchecked(index).id);
}
[[nodiscard]] bool id_alive(stable_id id) const noexcept
requires(HashIndexEnabled) {
return id <= static_cast<stable_id>(std::numeric_limits<internal_id>::max())
&& id < index_state_.ids.size()
&& (index_state_.ids[static_cast<size_type>(id)].flags & alive_flag) != 0;
}
void erase_by_id(stable_id id)
requires(HashIndexEnabled) {
erase(resolve_id(id));
}
void force_vector_mode() {
const auto previous_residency = residency_;
residency_ = ResidencyMode::forced_vector;
try {
convert_to(StorageMode::vector);
} catch (...) {
residency_ = previous_residency;
throw;
}
}
void force_tiered_mode() {
const auto previous_residency = residency_;
residency_ = ResidencyMode::forced_tiered;
try {
convert_to(StorageMode::tiered);
} catch (...) {
residency_ = previous_residency;
throw;
}
}
void force_tiered_mode(TieredConfig config) {
const auto previous_residency = residency_;
const auto previous_config = tiered_config_;
residency_ = ResidencyMode::forced_tiered;
try {
if (mode() == StorageMode::tiered) {
reconfigure_tiered(config);
} else {
tiered_config_ = config;
convert_to(StorageMode::tiered);
}
} catch (...) {
residency_ = previous_residency;
tiered_config_ = previous_config;
throw;
}
// on_transition(from, to, active_config) already synchronizes the
// built-in policy. Keep this compatibility hook after the successful
// storage commit so a failed rebuild cannot make policy and storage
// disagree, and so same-mode telemetry can compare old and new shapes.
if constexpr (requires(AdaptationPolicy& p) { p.set_tiered_config(config); }) {
policy_.set_tiered_config(tiered_config_);
}
}
void enable_auto_mode() noexcept { residency_ = ResidencyMode::automatic; }
void set_read_adaptation_mode(ReadAdaptationMode mode) noexcept {
read_adaptation_ = mode;
}
[[nodiscard]] ReadAdaptationMode read_adaptation_mode() const noexcept {
return read_adaptation_;
}
bool adapt_now() {
if (residency_ != ResidencyMode::automatic) {
return false;
}
return apply_pending_adaptation();
}
[[nodiscard]] T* data() noexcept
requires(!HashIndexEnabled) {
if (mode() != StorageMode::vector) {
return nullptr;
}
return std::get<vector_storage>(storage_).data();
}
[[nodiscard]] const T* data() const noexcept
requires(!HashIndexEnabled) {
if (mode() != StorageMode::vector) {
return nullptr;
}
return std::get<vector_storage>(storage_).data();
}
[[nodiscard]] std::optional<std::span<const T>> try_contiguous_view() const noexcept
requires(!HashIndexEnabled) {
if (mode() != StorageMode::vector) {
return std::nullopt;
}
const auto& values = std::get<vector_storage>(storage_);
return std::span<const T>(values.data(), values.size());
}
[[nodiscard]] std::span<const T> make_contiguous()
requires(!HashIndexEnabled) {
force_vector_mode();
const auto& values = std::get<vector_storage>(storage_);
return {values.data(), values.size()};
}
[[nodiscard]] std::vector<T> contiguous_copy() const {
std::vector<T> result;
result.reserve(size());
for_each([&](const T& value) { result.push_back(value); });
return result;
}
template <class Function>
void for_each(Function&& function) {
if (mode() == StorageMode::vector) {
for (auto& record : std::get<vector_storage>(storage_)) {
function(record_value(record));
}
} else {
std::get<tiered_storage>(storage_).for_each(
[&](record_type& record) { function(record_value(record)); });
}
observe_sequential_read(size());
}
template <class Function>
void for_each(Function&& function) const {
if (mode() == StorageMode::vector) {
for (const auto& record : std::get<vector_storage>(storage_)) {
function(record_value(record));
}
} else {
std::get<tiered_storage>(storage_).for_each(
[&](const record_type& record) { function(record_value(record)); });
}
observe_sequential_read(size());
}
iterator begin() noexcept { return iterator(this, 0); }
iterator end() noexcept { return iterator(this, size()); }
const_iterator begin() const noexcept { return const_iterator(this, 0); }
const_iterator end() const noexcept { return const_iterator(this, size()); }
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
[[nodiscard]] std::size_t allocated_bytes() const noexcept {
std::size_t bytes = sizeof(*this);
if (mode() == StorageMode::vector) {
bytes += std::get<vector_storage>(storage_).capacity() * sizeof(record_type);
} else {
bytes += std::get<tiered_storage>(storage_).allocated_bytes();
}
if constexpr (HashIndexEnabled) {
bytes += index_state_.values.allocated_bytes();
bytes += index_state_.ids.capacity() * sizeof(IdMetadata);
}
return bytes;
}
[[nodiscard]] AdaptationPolicy& policy() noexcept { return policy_; }
[[nodiscard]] const AdaptationPolicy& policy() const noexcept { return policy_; }
private:
static void set_location(IdMetadata& metadata,
bool tiered,
std::uint32_t primary,
std::uint32_t secondary) noexcept {
metadata.primary = primary;
metadata.secondary = secondary;
if (tiered) {
metadata.flags |= tiered_flag;
} else {
metadata.flags &= static_cast<std::uint8_t>(~tiered_flag);
}
}
static T& record_value(record_type& record) noexcept {
if constexpr (HashIndexEnabled) {
return record.value;
} else {
return record;
}
}
static const T& record_value(const record_type& record) noexcept {
if constexpr (HashIndexEnabled) {
return record.value;
} else {
return record;
}
}
static internal_id record_id(const record_type& record) noexcept {
if constexpr (HashIndexEnabled) {
return record.id;
} else {
(void)record;
return invalid_internal_id;
}
}
record_type make_record(T value) {
if constexpr (HashIndexEnabled) {
if (index_state_.ids.size() >= static_cast<size_type>(invalid_internal_id)) {
throw std::length_error("stable id space exhausted");
}
const auto id = static_cast<internal_id>(index_state_.ids.size());
index_state_.ids.emplace_back();
return IndexedRecord{std::move(value), id};
} else {
return value;
}
}
record_type& record_at_unchecked(size_type index) noexcept {
return mode() == StorageMode::vector
? std::get<vector_storage>(storage_)[index]
: std::get<tiered_storage>(storage_)[index];
}
const record_type& record_at_unchecked(size_type index) const noexcept {
return mode() == StorageMode::vector
? std::get<vector_storage>(storage_)[index]
: std::get<tiered_storage>(storage_)[index];
}
T& value_at_unchecked(size_type index) noexcept {
return record_value(record_at_unchecked(index));
}
const T& value_at_unchecked(size_type index) const noexcept {
return record_value(record_at_unchecked(index));
}
void check_index(size_type index) const {
if (index >= size()) {
throw std::out_of_range("AdaptiveSequence index out of range");
}
}
void push_back_impl(T value) {
apply_pending_adaptation();
const auto old_size = size();
auto record = make_record(std::move(value));
const auto id = record_id(record);
auto relocate = relocation_callback();
if (mode() == StorageMode::vector) {
auto& values = std::get<vector_storage>(storage_);
values.push_back(std::move(record));
refresh_vector_locations(old_size);
} else {
std::get<tiered_storage>(storage_).push_back(std::move(record), relocate);
}
if constexpr (HashIndexEnabled) {
auto& metadata = index_state_.ids[static_cast<size_type>(id)];
metadata.flags |= alive_flag;
link_value(value_at_unchecked(old_size), id);
}
++generation_;
policy_.observe({OperationKind::append, old_size, old_size, 1, sizeof(T)});
}
void insert_impl(size_type index, T value) {
if (index > size()) {
throw std::out_of_range("AdaptiveSequence insertion index out of range");
}
apply_pending_adaptation();
const auto old_size = size();
auto record = make_record(std::move(value));
const auto id = record_id(record);
auto relocate = relocation_callback();
if (mode() == StorageMode::vector) {
auto& values = std::get<vector_storage>(storage_);
values.insert(values.begin() + static_cast<difference_type>(index), std::move(record));
refresh_vector_locations(index);
} else {
std::get<tiered_storage>(storage_).insert(index, std::move(record), relocate);
}
if constexpr (HashIndexEnabled) {
auto& metadata = index_state_.ids[static_cast<size_type>(id)];
metadata.flags |= alive_flag;
link_value(value_at_unchecked(index), id);
}
++generation_;
observe_structural_edit(OperationKind::insert, old_size, index);
}
void set_impl(size_type index, T value) {
check_index(index);
auto& record = record_at_unchecked(index);
if constexpr (HashIndexEnabled) {
if (equal_(record.value, value)) {
record.value = std::move(value);
} else {
const auto id = record.id;
unlink_value(record.value, id);
record.value = std::move(value);
link_value(record.value, id);
}
} else {
record = std::move(value);
}
policy_.observe({OperationKind::set, size(), index, 1, sizeof(T)});
}
auto relocation_callback() {
return [this](const record_type& record,
typename tiered_storage::leaf_id_type leaf,
size_type local) {
if constexpr (HashIndexEnabled) {
auto& metadata = index_state_.ids[static_cast<size_type>(record.id)];
set_location(metadata, true, static_cast<std::uint32_t>(leaf),
static_cast<std::uint32_t>(local));
} else {
(void)record;
(void)leaf;
(void)local;
}
};
}
void refresh_vector_locations(size_type first) noexcept {
if constexpr (HashIndexEnabled) {
const auto& values = std::get<vector_storage>(storage_);
for (auto i = first; i < values.size(); ++i) {
set_location(index_state_.ids[static_cast<size_type>(values[i].id)],
false, static_cast<std::uint32_t>(i), 0);
}
} else {
(void)first;
}
}
void link_value(const T& value, internal_id id)
requires(HashIndexEnabled) {
auto& entry = index_state_.values.ensure(value);
auto& metadata = index_state_.ids[static_cast<size_type>(id)];
metadata.previous = invalid_internal_id;
metadata.next = entry.head;
if (entry.head != invalid_internal_id) {
index_state_.ids[static_cast<size_type>(entry.head)].previous = id;
}
entry.head = id;
++entry.count;
}
void unlink_value(const T& value, internal_id id)
requires(HashIndexEnabled) {
auto* entry = index_state_.values.find(value);
if (!entry) {
throw std::logic_error("hash index invariant violated");
}
auto& metadata = index_state_.ids[static_cast<size_type>(id)];
if (metadata.previous != invalid_internal_id) {
index_state_.ids[static_cast<size_type>(metadata.previous)].next = metadata.next;
} else {
entry->head = metadata.next;
}
if (metadata.next != invalid_internal_id) {
index_state_.ids[static_cast<size_type>(metadata.next)].previous = metadata.previous;
}
metadata.previous = invalid_internal_id;
metadata.next = invalid_internal_id;
--entry->count;
if (entry->count == 0) {
index_state_.values.erase_key(value);
}
}
[[nodiscard]] size_type resolve_id(stable_id id) const
requires(HashIndexEnabled) {
if (id >= index_state_.ids.size()) {
throw std::out_of_range("unknown stable id");
}
const auto& metadata = index_state_.ids[static_cast<size_type>(id)];
if ((metadata.flags & alive_flag) == 0) {
throw std::out_of_range("stable id no longer refers to an element");
}
if ((metadata.flags & tiered_flag) == 0) {
return metadata.primary;
}
return std::get<tiered_storage>(storage_).logical_index(
static_cast<typename tiered_storage::leaf_id_type>(metadata.primary),
metadata.secondary);
}
bool apply_pending_adaptation() {
if (residency_ != ResidencyMode::automatic || !policy_.decision_ready()) {
return false;
}
if constexpr (requires(AdaptationPolicy& p, StorageMode current,
size_type count, TieredConfig config) {
p.recommended_decision(current, count, config);
}) {
const auto recommendation =
policy_.recommended_decision(mode(), size(), tiered_config_);
if (!recommendation) {
return false;
}
if (recommendation->target == mode()) {
if (mode() == StorageMode::tiered
&& recommendation->tiered_config != tiered_config_) {
reconfigure_tiered(recommendation->tiered_config);
return true;
}
return false;
}
if (recommendation->target == StorageMode::tiered) {
const auto previous_config = tiered_config_;
tiered_config_ = recommendation->tiered_config;
try {
convert_to(recommendation->target);
} catch (...) {
// The recommended shape is policy state, not committed
// container state. A failed allocation/copy must not make
// tiered_config() describe storage that was never built.
tiered_config_ = previous_config;
throw;
}
return true;
}
convert_to(recommendation->target);
return true;
} else {
const auto recommendation = policy_.recommended_mode(mode(), size());
if (!recommendation || *recommendation == mode()) {
return false;
}
convert_to(*recommendation);
return true;
}
}
void convert_to(StorageMode target) {
const auto source = mode();
if (source == target) {
return;
}
if (target == StorageMode::tiered) {
tiered_storage tiered;
if constexpr (std::is_copy_constructible_v<record_type>) {
tiered = tiered_storage::from_vector_copy(
std::get<vector_storage>(storage_), tiered_config_);
} else {
auto values = std::move(std::get<vector_storage>(storage_));
tiered = tiered_storage::from_vector(std::move(values), tiered_config_);
}
tiered_config_ = tiered.config();
storage_.template emplace<tiered_storage>(std::move(tiered));
refresh_tiered_locations();
} else {
vector_storage values;
if constexpr (std::is_copy_constructible_v<record_type>) {
values = std::get<tiered_storage>(storage_).to_vector_copy();
} else {
values = std::get<tiered_storage>(storage_).to_vector_move();
}
storage_.template emplace<vector_storage>(std::move(values));
refresh_vector_locations(0);
}
++generation_;
notify_policy_transition(source, target);
}
void reconfigure_tiered(TieredConfig target_config) {
if (mode() != StorageMode::tiered) {
tiered_config_ = target_config;
return;
}
if (target_config == tiered_config_) {
return;
}
if (target_config.leaf_capacity == tiered_config_.leaf_capacity) {
auto& tiered = std::get<tiered_storage>(storage_);
tiered.reconfigure_directory(target_config.directory_fanout,
target_config.directory_levels);
tiered_config_ = tiered.config();
++generation_;
notify_policy_transition(StorageMode::tiered, StorageMode::tiered);
return;
}
tiered_storage rebuilt;
if constexpr (std::is_copy_constructible_v<record_type>) {
rebuilt = tiered_storage::reconfigured_copy(
std::get<tiered_storage>(storage_), target_config);
} else {
auto values = std::get<tiered_storage>(storage_).to_vector_move();
rebuilt = tiered_storage::from_vector(std::move(values), target_config);
}
tiered_config_ = rebuilt.config();
storage_.template emplace<tiered_storage>(std::move(rebuilt));
refresh_tiered_locations();
++generation_;
notify_policy_transition(StorageMode::tiered, StorageMode::tiered);
}
void notify_policy_transition(StorageMode source, StorageMode target) noexcept {
if constexpr (requires(AdaptationPolicy& p, StorageMode from,
StorageMode to, TieredConfig config) {
p.on_transition(from, to, config);
}) {
policy_.on_transition(source, target, tiered_config_);
} else {
policy_.on_transition(source, target);
}
}
void refresh_tiered_locations() noexcept {
if constexpr (HashIndexEnabled) {
auto relocate = relocation_callback();
std::get<tiered_storage>(storage_).for_each_with_location(relocate);
}
}
[[nodiscard]] std::size_t configured_read_sample_rate() const noexcept {
if constexpr (requires(const AdaptationPolicy& p) { p.config().read_sample_rate; }) {
return std::max<std::size_t>(1, policy_.config().read_sample_rate);
} else {
return 256;
}
}
[[nodiscard]] std::size_t configured_edit_sample_rate() const noexcept {
if constexpr (requires(const AdaptationPolicy& p) { p.config().edit_sample_rate; }) {
return std::max<std::size_t>(1, policy_.config().edit_sample_rate);
} else {
return 1;
}
}
void observe_structural_edit(OperationKind kind,
size_type old_size,
size_type position) noexcept {
if (--edit_sample_countdown_ == 0) {
edit_sample_countdown_ = edit_sample_rate_;
policy_.observe({kind, old_size, position, edit_sample_rate_, sizeof(T)});
}
}
void observe_random_read() const noexcept {
if (--read_sample_countdown_ == 0) {
read_sample_countdown_ = read_sample_rate_;
policy_.observe({OperationKind::random_read, size(), 0, read_sample_rate_, sizeof(T)});
}
}
void observe_sequential_read(size_type count) const noexcept {
if (count != 0) {
policy_.observe({OperationKind::sequential_read, size(), 0, count, sizeof(T)});
}
}
TieredConfig tiered_config_;
mutable AdaptationPolicy policy_;
std::variant<vector_storage, tiered_storage> storage_;
[[no_unique_address]] index_state_type index_state_{};
[[no_unique_address]] Equal equal_{};
ResidencyMode residency_ = ResidencyMode::automatic;
ReadAdaptationMode read_adaptation_ = ReadAdaptationMode::deferred;
std::size_t read_sample_rate_ = 256;
mutable std::size_t read_sample_countdown_ = 256;
std::size_t edit_sample_rate_ = 1;
std::size_t edit_sample_countdown_ = 1;
std::uint64_t generation_ = 0;
};
} // namespace uc