207 lines
7.4 KiB
C++
207 lines
7.4 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace uc::detail {
|
|
|
|
// A compact directory over leaf sizes. Level zero describes leaves, every
|
|
// next level groups `fanout` children, and the requested maximum number of
|
|
// levels is respected. The top level uses binary search; descent only scans a
|
|
// single fanout-sized group. A Fenwick side index supplies O(log L) prefix
|
|
// sums needed by stable-ID -> logical-index resolution.
|
|
class HierarchicalSizeIndex {
|
|
public:
|
|
struct Location {
|
|
std::size_t leaf = 0;
|
|
std::size_t local = 0;
|
|
};
|
|
|
|
HierarchicalSizeIndex(std::size_t fanout = 16, std::size_t max_levels = 3)
|
|
: fanout_(std::max<std::size_t>(2, fanout)),
|
|
max_levels_(std::clamp<std::size_t>(max_levels, 1, 8)) {}
|
|
|
|
void configure(std::size_t fanout, std::size_t max_levels) {
|
|
const auto leaf_sizes = levels_.empty()
|
|
? std::vector<std::size_t>{} : levels_.front();
|
|
HierarchicalSizeIndex replacement(fanout, max_levels);
|
|
replacement.rebuild(leaf_sizes);
|
|
swap(replacement);
|
|
}
|
|
|
|
void rebuild(const std::vector<std::size_t>& leaf_sizes) {
|
|
// Build all allocation-owning state off to the side. A failed
|
|
// allocation must not leave a directory that no longer describes its
|
|
// storage.
|
|
std::vector<std::vector<std::size_t>> new_levels;
|
|
std::vector<std::size_t> new_top_prefix;
|
|
std::vector<std::size_t> new_fenwick(leaf_sizes.size() + 1, 0);
|
|
std::size_t new_total = 0;
|
|
if (leaf_sizes.empty()) {
|
|
levels_.swap(new_levels);
|
|
top_prefix_.swap(new_top_prefix);
|
|
fenwick_.swap(new_fenwick);
|
|
total_ = 0;
|
|
return;
|
|
}
|
|
|
|
new_levels.push_back(leaf_sizes);
|
|
// Keep the top directory at most fanout-sized. Binary search handles
|
|
// that top level, so building extra single-parent levels only adds a
|
|
// lookup and cannot improve asymptotic complexity.
|
|
while (new_levels.back().size() > fanout_
|
|
&& new_levels.size() < max_levels_) {
|
|
const auto& children = new_levels.back();
|
|
std::vector<std::size_t> parents;
|
|
parents.reserve((children.size() + fanout_ - 1) / fanout_);
|
|
for (std::size_t begin = 0; begin < children.size(); begin += fanout_) {
|
|
const auto end = std::min(children.size(), begin + fanout_);
|
|
std::size_t weight = 0;
|
|
for (auto i = begin; i < end; ++i) {
|
|
weight += children[i];
|
|
}
|
|
parents.push_back(weight);
|
|
}
|
|
new_levels.push_back(std::move(parents));
|
|
}
|
|
|
|
for (const auto weight : new_levels.back()) {
|
|
new_total += weight;
|
|
new_top_prefix.push_back(new_total);
|
|
}
|
|
for (std::size_t i = 0; i < leaf_sizes.size(); ++i) {
|
|
for (auto node = i + 1; node < new_fenwick.size();
|
|
node += node & (~node + 1)) {
|
|
new_fenwick[node] += leaf_sizes[i];
|
|
}
|
|
}
|
|
|
|
levels_.swap(new_levels);
|
|
top_prefix_.swap(new_top_prefix);
|
|
fenwick_.swap(new_fenwick);
|
|
total_ = new_total;
|
|
}
|
|
|
|
void swap(HierarchicalSizeIndex& other) noexcept {
|
|
using std::swap;
|
|
swap(fanout_, other.fanout_);
|
|
swap(max_levels_, other.max_levels_);
|
|
swap(total_, other.total_);
|
|
levels_.swap(other.levels_);
|
|
top_prefix_.swap(other.top_prefix_);
|
|
fenwick_.swap(other.fenwick_);
|
|
}
|
|
|
|
void update(std::size_t leaf, std::ptrdiff_t delta) {
|
|
if (levels_.empty() || leaf >= levels_.front().size()) {
|
|
throw std::out_of_range("directory leaf index out of range");
|
|
}
|
|
apply_delta(levels_[0][leaf], delta);
|
|
std::size_t node = leaf;
|
|
for (std::size_t level = 1; level < levels_.size(); ++level) {
|
|
node /= fanout_;
|
|
apply_delta(levels_[level][node], delta);
|
|
}
|
|
total_ = apply_delta_copy(total_, delta);
|
|
fenwick_add_signed(leaf, delta);
|
|
rebuild_top_prefix();
|
|
}
|
|
|
|
[[nodiscard]] Location locate(std::size_t logical_index) const {
|
|
if (logical_index >= total_ || levels_.empty()) {
|
|
throw std::out_of_range("logical index out of range");
|
|
}
|
|
|
|
const auto top_it = std::upper_bound(top_prefix_.begin(), top_prefix_.end(), logical_index);
|
|
std::size_t node = static_cast<std::size_t>(top_it - top_prefix_.begin());
|
|
std::size_t remaining = logical_index - (node == 0 ? 0 : top_prefix_[node - 1]);
|
|
|
|
for (std::size_t level = levels_.size() - 1; level > 0; --level) {
|
|
const auto& children = levels_[level - 1];
|
|
const auto begin = node * fanout_;
|
|
const auto end = std::min(children.size(), begin + fanout_);
|
|
auto child = begin;
|
|
for (; child < end; ++child) {
|
|
if (remaining < children[child]) {
|
|
break;
|
|
}
|
|
remaining -= children[child];
|
|
}
|
|
assert(child < end);
|
|
node = child;
|
|
}
|
|
return {node, remaining};
|
|
}
|
|
|
|
[[nodiscard]] std::size_t prefix_before(std::size_t leaf) const noexcept {
|
|
std::size_t sum = 0;
|
|
for (std::size_t i = leaf; i > 0; i -= i & (~i + 1)) {
|
|
sum += fenwick_[i];
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
[[nodiscard]] std::size_t total() const noexcept { return total_; }
|
|
[[nodiscard]] std::size_t actual_levels() const noexcept { return levels_.size(); }
|
|
[[nodiscard]] std::size_t fanout() const noexcept { return fanout_; }
|
|
|
|
[[nodiscard]] std::size_t allocated_bytes() const noexcept {
|
|
std::size_t bytes = fenwick_.capacity() * sizeof(std::size_t)
|
|
+ top_prefix_.capacity() * sizeof(std::size_t);
|
|
for (const auto& level : levels_) {
|
|
bytes += level.capacity() * sizeof(std::size_t);
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
private:
|
|
static void apply_delta(std::size_t& value, std::ptrdiff_t delta) {
|
|
value = apply_delta_copy(value, delta);
|
|
}
|
|
|
|
static std::size_t apply_delta_copy(std::size_t value, std::ptrdiff_t delta) {
|
|
if (delta < 0) {
|
|
const auto magnitude = static_cast<std::size_t>(-delta);
|
|
assert(value >= magnitude);
|
|
return value - magnitude;
|
|
}
|
|
return value + static_cast<std::size_t>(delta);
|
|
}
|
|
|
|
void rebuild_top_prefix() {
|
|
top_prefix_.clear();
|
|
top_prefix_.reserve(levels_.back().size());
|
|
std::size_t prefix = 0;
|
|
for (const auto weight : levels_.back()) {
|
|
prefix += weight;
|
|
top_prefix_.push_back(prefix);
|
|
}
|
|
}
|
|
|
|
void fenwick_add(std::size_t leaf, std::size_t delta) noexcept {
|
|
for (auto i = leaf + 1; i < fenwick_.size(); i += i & (~i + 1)) {
|
|
fenwick_[i] += delta;
|
|
}
|
|
}
|
|
|
|
void fenwick_add_signed(std::size_t leaf, std::ptrdiff_t delta) noexcept {
|
|
for (auto i = leaf + 1; i < fenwick_.size(); i += i & (~i + 1)) {
|
|
fenwick_[i] = apply_delta_copy(fenwick_[i], delta);
|
|
}
|
|
}
|
|
|
|
std::size_t fanout_;
|
|
std::size_t max_levels_;
|
|
std::size_t total_ = 0;
|
|
std::vector<std::vector<std::size_t>> levels_;
|
|
std::vector<std::size_t> top_prefix_;
|
|
std::vector<std::size_t> fenwick_;
|
|
};
|
|
|
|
} // namespace uc::detail
|