Everett
Loading...
Searching...
No Matches
sampling.h
Go to the documentation of this file.
1
13#pragma once
14
16
18
19#include <algorithm>
20#include <concepts>
21#include <cstddef>
22#include <cstdint>
23#include <limits>
24#include <memory>
25#include <stdexcept>
26#include <utility>
27
28namespace everett {
29 template <class P> struct profile_sample {
30 using policy_type = P;
32 std::uint64_t target_ordinal = 0;
33 };
34
35 template <class P> struct profile_sample_view {
36 using policy_type = P;
38 std::uint64_t target_ordinal = 0;
40 std::uint64_t source_ordinal = 0;
41 };
42
43 // A typed in-memory handoff, not a serialized count-code format. Backspace
44 // counts P units from the previously emitted sample; suffix owns only the
45 // remaining key bits. The first sample is literal (backspace == 0).
46 template <class P> struct profile_coded_sample {
47 using policy_type = P;
48 std::uint64_t backspace = 0;
50 std::uint64_t target_ordinal = 0;
51 };
52
53 namespace sampling_detail {
54 template <class P> void check_ordinal(std::uint64_t count, std::uint64_t ordinal) {
55 if (count > std::numeric_limits<std::uint64_t>::max() / P::group_size || ordinal != count * P::group_size)
56 error_detail::raise<std::invalid_argument>("sample ordinals must be consecutive policy groups");
57 }
58
59 // Suffix must not alias context. Allocate before changing its retained
60 // prefix, then the bounded resize/copy operations cannot allocate or fail.
61 // Preserving a prefix does not copy it unless the buffer must grow; growth
62 // is geometric so gradually lengthening keys do not reallocate each time.
63 inline void replace_suffix(bit_string & context, std::uint64_t retained, bit_view suffix) {
64 auto bits = profile_detail::add(retained, suffix.size());
65 if (bits > std::numeric_limits<std::uint64_t>::max() - 7)
66 error_detail::raise<std::length_error>("sample key is too large");
67 auto bytes = profile_detail::byte_count(bits);
68 if (bytes > context.bytes.max_size()) error_detail::raise<std::length_error>("sample key is too large");
69 if (bytes > context.bytes.capacity()) {
70 auto capacity = context.bytes.capacity();
71 auto grown = capacity > (context.bytes.max_size() >> 1) ? context.bytes.max_size() : capacity << 1;
72 context.bytes.reserve(std::max(static_cast<std::size_t>(bytes), grown));
73 }
74 profile_detail::resize(context, retained);
75 profile_detail::append(context, suffix);
76 }
77 }
78
79 // One reusable reconstructed key per endpoint. Validation/allocation failure
80 // leaves the preceding accepted key and ordinal intact. key() borrows that
81 // context until the next successful encode/accept, move, or destruction.
82 template <class P> struct profile_sample_encoder {
83 using policy_type = P;
84
85 profile_coded_sample<P> encode(bit_view key, std::uint64_t target_ordinal) {
86 sampling_detail::check_ordinal<P>(count_, target_ordinal);
87 if (key.size() & (P::bits_per_unit - 1)) error_detail::raise<std::invalid_argument>("sample key unit mismatch");
88 auto previous = key_.view();
89 auto comparison = compare_common_bits<typename P::architecture>(previous, key);
90 if (comparison.order > 0) error_detail::raise<std::invalid_argument>("sample keys must be sorted");
91 return encode_known(key, target_ordinal, comparison.common_bits);
92 }
93
94 bit_view key() const & { return key_.view(); }
95 bit_view key() const && = delete;
96 std::uint64_t size() const noexcept { return count_; }
97
98 private:
99 template <class, class, class> friend struct index_builder;
100 // The builder supplies exact comparison state for sorted, unit-aligned
101 // keys and consecutive output ordinals; the public entry remains checked.
102 profile_coded_sample<P> encode_known(bit_view key, std::uint64_t target_ordinal,
103 std::uint64_t common_bits) {
104 auto retained = common_bits >> P::unit_shift;
105 auto retained_bits = profile_detail::multiply(retained, P::bits_per_unit);
106 // Copy the transmitted suffix before editing context. Input may alias
107 // this encoder's current key, including a subview of that key.
108 profile_coded_sample<P> result{(key_.bit_size >> P::unit_shift) - retained,
109 bit_string::copy(key.subview(retained_bits, key.size() - retained_bits)), target_ordinal};
110 sampling_detail::replace_suffix(key_, retained_bits, result.suffix.view());
111 ++count_;
112 return result;
113 }
114
116 std::uint64_t count_ = 0;
117 };
118
119 template <class P> struct profile_sample_decoder {
120 using policy_type = P;
121
122 bit_view accept(profile_coded_sample<P> const & sample) { return accept_compared(sample).first; }
123
124 bit_view key() const & { return key_.view(); }
125 bit_view key() const && = delete;
126 std::uint64_t size() const noexcept { return count_; }
127
128 private:
129 template <class, class, class> friend struct index_builder;
130 // Only the index builder uses this full-key path. Its input cannot borrow
131 // the private decoder context; reserve before editing its retained prefix.
132 bit_comparison accept_full(bit_view key, std::uint64_t ordinal) {
133 sampling_detail::check_ordinal<P>(count_, ordinal);
134 if (key.size() & (P::bits_per_unit - 1))
135 error_detail::raise<std::invalid_argument>("sample key unit mismatch");
136 auto comparison = compare_common_bits<typename P::architecture>(key_.view(), key);
137 if (comparison.order > 0)
138 error_detail::raise<std::invalid_argument>("sample keys must be sorted");
139 auto retained = comparison.common_bits & ~std::uint64_t{7};
140 sampling_detail::replace_suffix(key_, retained, key.subview(retained, key.size() - retained));
141 ++count_;
142 return comparison;
143 }
144 std::pair<bit_view, bit_comparison> accept_compared(profile_coded_sample<P> const & sample) {
145 sampling_detail::check_ordinal<P>(count_, sample.target_ordinal);
146 auto suffix = sample.suffix.view();
147 if (suffix.size() & (P::bits_per_unit - 1)) error_detail::raise<std::invalid_argument>("sample suffix unit mismatch");
148 auto previous = key_.view();
149 auto previous_units = previous.size() >> P::unit_shift;
150 if (sample.backspace > previous_units) error_detail::raise<std::invalid_argument>("sample backspace exceeds previous key");
151 auto retained = profile_detail::multiply(previous_units - sample.backspace, P::bits_per_unit);
152 // Both keys share the retained prefix. Comparing only the two remaining
153 // suffixes validates order without another full-key reconstruction.
154 auto comparison = compare_common_bits<typename P::architecture>(previous.subview(retained, previous.size() - retained), suffix);
155 if (comparison.order > 0)
156 error_detail::raise<std::invalid_argument>("sample keys must be sorted");
157 comparison.common_bits += retained;
158 sampling_detail::replace_suffix(key_, retained, suffix);
159 ++count_;
160 return {key_.view(), comparison};
161 }
162
164 std::uint64_t count_ = 0;
165 };
166
168 std::uint64_t native_entries = 0;
169 std::uint64_t borrowed_entries = 0;
170 std::uint64_t decoded_entries = 0;
171 std::uint64_t key_comparisons = 0;
172
173 std::uint64_t consumed_entries() const noexcept { return native_entries + borrowed_entries; }
174 };
175
176 // Sequential samples of one exact, immutable native/index pair. Both streams
177 // retain their own front-coding context. Construction decodes at most two
178 // records; advance consumes at most P::group_size merged occurrences. Native
179 // entries precede equal borrowed entries, and borrowed duplicates remain.
180 //
181 // These are record-work bounds: decoding/comparison still pays for key bits.
182 // Scratch space holds two current reconstructed keys. Values are never
183 // copied, and no array of reconstructed keys or samples is materialized.
184 // peek() borrows cursor scratch until the next advance, move, or destruction.
185 // A moved-to cursor remains valid; fresh peek() calls derive fresh key views.
186 template <class P, class Target = profile_blob<P>> struct sample_cursor {
187 using policy_type = P;
188 using target_type = Target;
189 static_assert(std::same_as<typename Target::policy_type, P>);
190 static constexpr std::uint64_t group_size = P::group_size;
191
192 explicit sample_cursor(std::shared_ptr<target_type const> target)
193 : sample_cursor(bind_target(std::move(target))) {}
194
195 bool done() const noexcept { return !target_ || (native_.done() && borrowed_.done()); }
196
198 if (done()) error_detail::raise<std::out_of_range>("sample cursor at end");
199 auto item = next_borrowed_ ? borrowed_.peek() : native_.peek();
201 item.ordinal};
202 }
203 profile_sample_view<P> peek() const && = delete;
204
205 void advance() {
206 if (done()) error_detail::raise<std::out_of_range>("sample cursor at end");
207 auto count = std::min(group_size, target_->virtual_size() - ordinal_);
208 for (std::uint64_t i = 0; i != count; ++i) {
209 if (next_borrowed_) {
213 } else {
217 }
218 ++ordinal_;
219 choose_next();
220 }
221 }
222
223 std::shared_ptr<target_type const> target() const noexcept { return target_; }
224 sampling_work const & counters() const noexcept { return work_; }
225
226 private:
227 struct binding {
228 std::shared_ptr<target_type const> target;
230 };
231 static binding bind_target(std::shared_ptr<target_type const> target) {
232 if (!target) error_detail::raise<std::invalid_argument>("sample cursor requires a pinned target");
233 auto view = target->view();
234 return {std::move(target), view};
235 }
236 explicit sample_cursor(binding source)
237 : target_(std::move(source.target)), native_(source.view.native()), borrowed_(source.view.borrowed()) {
238 work_.decoded_entries = std::uint64_t(!native_.done()) + std::uint64_t(!borrowed_.done());
239 choose_next();
240 }
241
242 void choose_next() {
243 if (native_.done()) next_borrowed_ = true;
244 else if (borrowed_.done()) next_borrowed_ = false;
245 else {
247 next_borrowed_ = compare_bits<typename P::architecture>(borrowed_.peek().key.prefix, native_.peek().key.prefix) < 0;
248 }
249 }
250
251 std::shared_ptr<target_type const> target_;
255 std::uint64_t ordinal_ = 0;
256 bool next_borrowed_ = false;
257 };
258}
Outlines exceptional check failures while preserving their types and messages.
std::uint64_t add(std::uint64_t a, std::uint64_t b)
Definition profile.h:39
std::uint64_t multiply(std::uint64_t a, std::uint64_t b)
Definition profile.h:44
std::uint64_t byte_count(std::uint64_t bits) noexcept
Definition profile.h:50
void resize(bit_string &value, std::uint64_t bits)
Definition profile.h:465
void append(bit_string &target, bit_view source)
Definition profile.h:480
void check_ordinal(std::uint64_t count, std::uint64_t ordinal)
Definition sampling.h:54
void replace_suffix(bit_string &context, std::uint64_t retained, bit_view suffix)
Definition sampling.h:63
Definition active_engine.h:18
stream_role
Definition policy.h:25
Declares Everett's profile blob support.
Definition profile.h:209
Definition profile.h:166
std::vector< std::byte > bytes
Definition profile.h:167
bit_view view() const &
Definition profile.h:178
static bit_string copy(bit_view source)
Definition profile.h:181
std::uint64_t bit_size
Definition profile.h:168
Definition profile.h:56
std::uint64_t size() const noexcept
Definition profile.h:64
bit_view subview(std::uint64_t first, std::uint64_t count) const
Definition profile.h:73
Definition index_builder.h:47
Definition profile_blob.h:73
Definition sampling.h:46
P policy_type
Definition sampling.h:47
std::uint64_t target_ordinal
Definition sampling.h:50
std::uint64_t backspace
Definition sampling.h:48
bit_string suffix
Definition sampling.h:49
Definition profile.h:1066
bool done() const noexcept
Definition profile.h:1091
profile_item< P > peek() const &
Definition profile.h:1093
void advance()
Definition profile.h:1103
Definition sampling.h:119
bit_string key_
Definition sampling.h:163
bit_comparison accept_full(bit_view key, std::uint64_t ordinal)
Definition sampling.h:132
std::uint64_t size() const noexcept
Definition sampling.h:126
P policy_type
Definition sampling.h:120
bit_view accept(profile_coded_sample< P > const &sample)
Definition sampling.h:122
std::uint64_t count_
Definition sampling.h:164
std::pair< bit_view, bit_comparison > accept_compared(profile_coded_sample< P > const &sample)
Definition sampling.h:144
bit_view key() const &&=delete
bit_view key() const &
Definition sampling.h:124
Definition sampling.h:82
profile_coded_sample< P > encode_known(bit_view key, std::uint64_t target_ordinal, std::uint64_t common_bits)
Definition sampling.h:102
std::uint64_t size() const noexcept
Definition sampling.h:96
std::uint64_t count_
Definition sampling.h:116
profile_coded_sample< P > encode(bit_view key, std::uint64_t target_ordinal)
Definition sampling.h:85
bit_view key() const &&=delete
bit_string key_
Definition sampling.h:115
bit_view key() const &
Definition sampling.h:94
P policy_type
Definition sampling.h:83
Definition sampling.h:35
std::uint64_t source_ordinal
Definition sampling.h:40
P policy_type
Definition sampling.h:36
stream_role source_role
Definition sampling.h:39
std::uint64_t target_ordinal
Definition sampling.h:38
bit_view key
Definition sampling.h:37
Definition sampling.h:29
bit_string key
Definition sampling.h:31
std::uint64_t target_ordinal
Definition sampling.h:32
P policy_type
Definition sampling.h:30
Definition sampling.h:227
profile_blob_view< P > view
Definition sampling.h:229
std::shared_ptr< target_type const > target
Definition sampling.h:228
Definition sampling.h:186
std::shared_ptr< target_type const > target_
Definition sampling.h:251
std::shared_ptr< target_type const > target() const noexcept
Definition sampling.h:223
P policy_type
Definition sampling.h:187
bool next_borrowed_
Definition sampling.h:256
void choose_next()
Definition sampling.h:242
profile_cursor< P, stream_role::borrowed > borrowed_
Definition sampling.h:253
bool done() const noexcept
Definition sampling.h:195
profile_cursor< P, stream_role::native > native_
Definition sampling.h:252
profile_sample_view< P > peek() const &&=delete
profile_sample_view< P > peek() const &
Definition sampling.h:197
Target target_type
Definition sampling.h:188
static binding bind_target(std::shared_ptr< target_type const > target)
Definition sampling.h:231
sampling_work work_
Definition sampling.h:254
sample_cursor(std::shared_ptr< target_type const > target)
Definition sampling.h:192
std::uint64_t ordinal_
Definition sampling.h:255
sampling_work const & counters() const noexcept
Definition sampling.h:224
static constexpr std::uint64_t group_size
Definition sampling.h:190
void advance()
Definition sampling.h:205
sample_cursor(binding source)
Definition sampling.h:236
Definition sampling.h:167
std::uint64_t consumed_entries() const noexcept
Definition sampling.h:173
std::uint64_t decoded_entries
Definition sampling.h:170
std::uint64_t borrowed_entries
Definition sampling.h:169
std::uint64_t key_comparisons
Definition sampling.h:171
std::uint64_t native_entries
Definition sampling.h:168