Pixie
Loading...
Searching...
No Matches
permuted_bit_block.h
1#pragma once
2
3// Current experiment snapshot, 2026-09-16. Ryzen 7 8845HS, Linux/WSL,
4// GCC 13.3, -O3 -DNDEBUG -march=native, Google Benchmark 1.9.4, CPU 0.
5// Command: cpp-bench native bit_sequence_benchmarks <filter> 5 0.15s
6// Filter: '^Block(Rotate|Read)<(DirectBlock|MappedBlock).*'
7// Median CPU ns/op; randomized repetition interleaving. Hot single block,
8// 256 precomputed rotations (seed 128), no construction/reset in timing.
9// Modes 0/1: chunk-aligned/unaligned proper subranges; 2: alternating 50/50;
10// 3: whole block by 65; 4: unaligned, N=2045, initial h=2038 (both variants
11// retain their circular origin). Modes 1/4 retain identity maps; mode 2 keeps
12// changing nonidentity maps across partial rotations without normalization.
13// Read setup: two aligned subrange rotations, then whole-block rotation by 65;
14// indexed reads use 1024 precomputed positions (seed 123). Flatten includes
15// producing all 32 output words with zero padding, without mutating the block.
16//
17// | Workload | N bits | Direct ns | Mapped ns |
18// | ---------------- | -----: | --------: | --------: |
19// | Rotate aligned | 2048 | 16.85 | 3.82 |
20// | Rotate unaligned | 2048 | 28.15 | 29.70 |
21// | Rotate mixed | 2048 | 22.03 | 48.95 |
22// | Rotate whole | 2048 | 3.29 | 3.29 |
23// | Rotate wrapped | 2045 | 29.65 | 31.11 |
24// | Indexed access | 2048 | 1.50 | 1.58 |
25// | Indexed access | 2045 | 1.50 | 1.56 |
26// | Flatten | 2048 | 10.57 | 99.69 |
27// | Flatten | 2045 | 11.36 | 99.55 |
28//
29// Direct/Mapped use the same 256-byte payload and packed copy primitive.
30// Object bytes: Direct 272, Mapped 280. Map adds 8 bytes (3.125% of payload).
31// Retain only as a bounded aligned-heavy experiment: mixed rotations and
32// flattening remain substantially slower than direct. These are block-only
33// results, not tree measurements. Chunk-wise gather/scatter avoids
34// normalization, not map cost. Independent hot indexed reads measure
35// throughput, not dependent latency; WSL timings do not establish cold-cache or
36// large-sequence performance.
37
38#include <pixie/bits.h>
39
40#include <algorithm>
41#include <array>
42#include <cassert>
43#include <cstddef>
44#include <cstdint>
45#include <span>
46#include <stdexcept>
47#include <type_traits>
48
49namespace pixie::experimental {
50
69template <bool Permuted = true>
71 public:
73 using value_type = bool;
75 static constexpr std::size_t capacity = 2048;
77 using Payload = std::array<std::uint64_t, 32>;
78
80 PermutedBitBlock() noexcept = default;
81
90 PermutedBitBlock(std::span<const std::uint64_t> words, std::size_t n)
91 : n_(n) {
92 if (n > 2048 || (n + 63) / 64 > words.size()) {
93 throw std::invalid_argument("PermutedBitBlock: input size");
94 }
95 std::copy_n(words.begin(), (n + 63) / 64, bits_.begin());
96 }
97
102 std::size_t size() const noexcept { return n_; }
107 bool empty() const noexcept { return n_ == 0; }
108
115 bool operator[](std::size_t i) const {
116 if (i >= n_) {
117 throw std::out_of_range("PermutedBitBlock: index");
118 }
119 const auto p = physical((h_ + i) % n_);
120 return (bits_[p / 64] >> (p % 64)) & 1;
121 }
122
128 Payload flatten() const noexcept {
129 Payload result{};
130 copy(0, result, 0, n_);
131 return result;
132 }
133
144 void rotate_left(std::size_t left,
145 std::size_t right,
146 std::size_t distance) noexcept {
147 assert(left <= right && right <= n_);
148 const auto length = right - left;
149 if (length == 0 || (distance %= length) == 0) {
150 return;
151 }
152 if (length == n_) {
153 h_ = (h_ + distance) % n_;
154 return;
155 }
156 if constexpr (Permuted) {
157 if (h_ == 0 && ((left | right | distance) % 128 == 0)) {
158 const auto shift = left / 128 * 4;
159 const auto width = length / 128 * 4;
160 const auto cut = distance / 128 * 4;
161 // Proper subrange: width < 64; distance is strictly inside the range.
162 const auto mask = (std::uint64_t{1} << width) - 1;
163 const auto slice = (map_ >> shift) & mask;
164 const auto rotated = (slice >> cut) | (slice << (width - cut));
165 map_ = (map_ & ~(mask << shift)) | ((rotated & mask) << shift);
166 return;
167 }
168 }
169 Payload scratch{};
170 copy(left + distance, scratch, 0, length - distance);
171 copy(left, scratch, length - distance, distance);
172 const auto position = (h_ + left) % n_;
173 if constexpr (Permuted) {
174 if (map_ != identity) {
175 auto p = position;
176 std::size_t source = 0;
177 while (source != length) {
178 const auto run = std::min({length - source, n_ - p, 128 - p % 128});
179 copy_packed_bits(scratch.data(), source, bits_.data(), physical(p),
180 run);
181 source += run;
182 p = (p + run == n_) ? 0 : p + run;
183 }
184 return;
185 }
186 }
187 const auto prefix = std::min(length, n_ - position);
188 copy_packed_bits(scratch.data(), 0, bits_.data(), position, prefix);
189 copy_packed_bits(scratch.data(), prefix, bits_.data(), 0, length - prefix);
190 }
191
196 std::size_t payload_capacity_bytes() const noexcept { return sizeof(bits_); }
201 std::size_t metadata_bytes() const noexcept {
202 return sizeof(*this) - sizeof(bits_);
203 }
204
214 void redistribute(PermutedBitBlock& rhs, std::size_t left_size) noexcept {
215 assert(this != &rhs);
216 const auto total = n_ + rhs.n_;
217 assert(left_size <= capacity && left_size <= total &&
218 total - left_size <= capacity);
219 Payload a{}, b{};
220 auto gather = [&](std::size_t start, Payload& out, std::size_t count) {
221 const auto first = start < n_ ? std::min(count, n_ - start) : 0;
222 copy(start, out, 0, first);
223 rhs.copy(start + first - std::min(start + first, n_), out, first,
224 count - first);
225 };
226 gather(0, a, left_size);
227 gather(left_size, b, total - left_size);
228 bits_ = a;
229 rhs.bits_ = b;
230 n_ = left_size;
231 rhs.n_ = total - left_size;
232 h_ = rhs.h_ = 0;
233 if constexpr (Permuted) {
234 map_ = rhs.map_ = identity;
235 }
236 }
237
238 private:
239 static constexpr std::uint64_t identity = 0xfedcba9876543210ULL;
240 struct NoMap {};
241
242 std::size_t physical(std::size_t p) const noexcept {
243 if constexpr (Permuted) {
244 return ((map_ >> (p / 128 * 4)) & 15) * 128 + p % 128;
245 }
246 return p;
247 }
248
249 void copy(std::size_t offset,
250 Payload& out,
251 std::size_t dest,
252 std::size_t count) const noexcept {
253 if (count == 0) {
254 return;
255 }
256 auto p = (h_ + offset) % n_;
257 while (count != 0) {
258 auto run = std::min(count, n_ - p);
259 if constexpr (Permuted) {
260 if (map_ != identity) {
261 run = std::min(run, 128 - p % 128);
262 }
263 }
264 copy_packed_bits(bits_.data(), physical(p), out.data(), dest, run);
265 count -= run;
266 dest += run;
267 p = (p + run == n_) ? 0 : p + run;
268 }
269 }
270
271 Payload bits_{};
272 std::size_t n_ = 0;
273 std::size_t h_ = 0;
274 [[no_unique_address]] std::conditional_t<Permuted, std::uint64_t, NoMap>
275 map_ = []() noexcept {
276 if constexpr (Permuted) {
277 return identity;
278 } else {
279 return NoMap{};
280 }
281 }();
282};
283
284} // namespace pixie::experimental
void redistribute(PermutedBitBlock &rhs, std::size_t left_size) noexcept
Repartition two distinct blocks without allocation or exceptions.
Definition permuted_bit_block.h:214
bool operator[](std::size_t i) const
Read a zero-based logical bit by value.
Definition permuted_bit_block.h:115
bool value_type
Logical element type returned by value.
Definition permuted_bit_block.h:73
std::size_t metadata_bytes() const noexcept
Query object metadata size; there is no dynamic allocation.
Definition permuted_bit_block.h:201
static constexpr std::size_t capacity
Maximum logical element count.
Definition permuted_bit_block.h:75
std::size_t size() const noexcept
Query the logical length.
Definition permuted_bit_block.h:102
Payload flatten() const noexcept
Copy logical bits to linear words without mutating the block.
Definition permuted_bit_block.h:128
bool empty() const noexcept
Query whether the block is empty.
Definition permuted_bit_block.h:107
PermutedBitBlock() noexcept=default
Construct empty; all storage is initialized.
std::array< std::uint64_t, 32 > Payload
Fixed owning payload, in least-significant-bit-first word order.
Definition permuted_bit_block.h:77
void rotate_left(std::size_t left, std::size_t right, std::size_t distance) noexcept
Rotate [left, right) left by distance modulo its length.
Definition permuted_bit_block.h:144
std::size_t payload_capacity_bytes() const noexcept
Query fixed payload capacity.
Definition permuted_bit_block.h:196
Definition excess.h:90