Pixie
Loading...
Searching...
No Matches
sequence_tree_policy.h
1#pragma once
2
3#include <pixie/permutations/detail/tree_policy.h>
4
5#include <bit>
6#include <cstddef>
7#include <memory>
8#include <type_traits>
9#include <utility>
10
12namespace pixie::experimental {
13
14// Lazy ordering is confined to leaf parents. Its activation flag shares the
15// node's count word; inactive union storage is never read. Uniform node sizes
16// allow the existing allocation pool to recycle across tree levels.
17template <std::size_t Fanout, class Order>
18class LeafChildOrder {
19 static_assert(Order::capacity == Fanout);
20 static_assert(std::is_nothrow_constructible_v<Order, std::size_t> &&
21 std::is_trivially_destructible_v<Order>);
22 union Storage {
23 Order value;
24 Storage() noexcept {}
25 } storage_;
26
27 public:
28 static constexpr bool supports_bias = false;
29 struct Header {
30 std::size_t count : std::bit_width(Fanout) = 0;
31 std::size_t regular_prefix : std::bit_width(Fanout) = 0;
32 std::size_t mapped : 1 = 0;
33 };
34 template <class Node>
35 std::size_t slot(const Node& node,
36 std::size_t i,
37 std::size_t height) const noexcept {
38 return height == 1 && node.mapped ? storage_.value[i] : i;
39 }
40 template <class Node>
41 void reset(Node& node) noexcept {
42 node.mapped = false;
43 }
44 template <class Tree, class Node>
45 std::size_t rotate(Node& node,
46 std::size_t begin,
47 std::size_t middle,
48 std::size_t end,
49 std::size_t height) noexcept {
50 if (height != 1) {
51 return permutations::detail::DirectChildOrder<Fanout>::template rotate<
52 Tree>(node, begin, middle, end, height);
53 }
54 if (!node.mapped) {
55 std::construct_at(&storage_.value, std::size_t(node.count));
56 node.mapped = true;
57#ifdef PIXIE_SEQUENCE_TREE_TESTING
58 ++Tree::test_counters.order_materializations;
59#endif
60 }
61 return storage_.value.rotate_children(node.children, begin, end,
62 middle - begin);
63 }
64 template <class Node>
65 bool valid(const Node& node, std::size_t height) const noexcept {
66 return !node.mapped ||
67 (height == 1 && storage_.value.size() == node.count &&
68 storage_.value.valid());
69 }
70};
71
72// Experimental bounded rotation reserve. Small owners retain nothing. Failed
73// preflight restores the old reserve footprint; only successful commits can
74// retain newly allocated spares up to Limit. No payload leaf is cached.
75template <class Node, class Allocator, std::size_t Limit>
76struct BoundedNodeReserve {
77 Node* nodes = nullptr;
78 std::size_t count = 0;
79 BoundedNodeReserve() = default;
80 BoundedNodeReserve(const BoundedNodeReserve&) = delete;
81 BoundedNodeReserve& operator=(const BoundedNodeReserve&) = delete;
82 BoundedNodeReserve(BoundedNodeReserve&& other) noexcept
83 : nodes(std::exchange(other.nodes, nullptr)),
84 count(std::exchange(other.count, 0)) {}
85 BoundedNodeReserve& operator=(BoundedNodeReserve&& other) noexcept {
86 if (this != &other) {
87 clear();
88 nodes = std::exchange(other.nodes, nullptr);
89 count = std::exchange(other.count, 0);
90 }
91 return *this;
92 }
93 ~BoundedNodeReserve() { clear(); }
94 void clear() noexcept {
95 while (nodes) {
96 auto* next = static_cast<Node*>(nodes->children[0]);
97 Allocator::dispose(nodes);
98 nodes = next;
99 }
100 count = 0;
101 }
102 BoundedNodeReserve* for_rotation(std::size_t size,
103 std::size_t value_bytes) noexcept {
104 return size >= (std::size_t{1} << 20) / value_bytes ? this : nullptr;
105 }
106 void trim(std::size_t size, std::size_t value_bytes) noexcept {
107 if (!for_rotation(size, value_bytes)) {
108 clear();
109 }
110 }
111 std::size_t node_count() const noexcept { return count; }
112};
113
114template <class Node, class Owner, class Allocator, std::size_t Limit>
115class RetainedNodePool
116 : public permutations::detail::SequenceNodePool<Node, Owner, Allocator> {
117 using Base = permutations::detail::SequenceNodePool<Node, Owner, Allocator>;
118 using Reserve = BoundedNodeReserve<Node, Allocator, Limit>;
119 Reserve* retained_ = nullptr;
120 std::size_t borrowed_ = 0;
121 std::size_t retain_limit_ = 0;
122
123 public:
124 explicit RetainedNodePool(Reserve* reserve = nullptr) noexcept
125 : retained_(reserve) {
126 if (retained_) {
127 this->nodes = std::exchange(retained_->nodes, nullptr);
128 borrowed_ = std::exchange(retained_->count, 0);
129 retain_limit_ = borrowed_;
130 }
131 }
132 void commit() noexcept { retain_limit_ = Limit; }
133 ~RetainedNodePool() {
134 if (retained_) {
135 while (this->nodes && retained_->count < retain_limit_) {
136 auto* next = static_cast<Node*>(this->nodes->children[0]);
137 this->nodes->children[0] = retained_->nodes;
138 retained_->nodes = this->nodes;
139 ++retained_->count;
140 this->nodes = next;
141 }
142 }
143 }
144 void reserve(std::size_t count, std::size_t leaf_spares) {
145 const auto available = std::exchange(borrowed_, 0);
146 Base::reserve(count > available ? count - available : 0, leaf_spares);
147 }
148};
149
150template <std::size_t ReservedNodes, class LeafOrder = void>
151struct SequenceTreePolicy {
152 template <std::size_t Fanout>
153 using Order =
154 std::conditional_t<std::is_void_v<LeafOrder>,
155 permutations::detail::DirectChildOrder<Fanout>,
156 LeafChildOrder<Fanout, LeafOrder>>;
157 template <class Node, class Allocator>
158 using Reserve =
159 std::conditional_t<ReservedNodes == 0,
160 permutations::detail::NoNodeReserve<Node>,
161 BoundedNodeReserve<Node, Allocator, ReservedNodes>>;
162 template <class Node, class Owner, class Allocator>
163 using Pool = std::conditional_t<
164 ReservedNodes == 0,
165 permutations::detail::SequenceNodePool<Node, Owner, Allocator>,
166 RetainedNodePool<Node, Owner, Allocator, ReservedNodes>>;
167 struct TestCounters {
168 std::size_t order_materializations = 0;
169 };
170};
171
172} // namespace pixie::experimental
Definition excess.h:90