Pixie
Loading...
Searching...
No Matches
samples.h
1#pragma once
2
3#include <pixie/bits.h>
4#include <pixie/rank_select/detail/compressed/metadata_array.h>
5#include <pixie/storage/aligned.h>
6
7#include <algorithm>
8#include <bit>
9#include <cassert>
10#include <cstddef>
11#include <cstdint>
12#include <limits>
13#include <optional>
14#include <span>
15#include <stdexcept>
16#include <utility>
17#include <vector>
18
19namespace pixie::detail::compressed {
20inline std::uint64_t read_monotone_low(const std::uint64_t* words,
21 std::size_t index,
22 unsigned width) {
23 if (width == 0) {
24 return 0;
25 }
26 const std::size_t bit = index * width;
27 const unsigned shift = bit % 64;
28 std::uint64_t value = words[bit / 64] >> shift;
29 if (shift + width > 64) {
30 value |= words[bit / 64 + 1] << (64 - shift);
31 }
32 return value & ((std::uint64_t{1} << width) - 1);
33}
34
35inline void write_monotone_low(std::uint64_t* words,
36 std::size_t index,
37 unsigned width,
38 std::uint64_t value) {
39 if (width == 0) {
40 return;
41 }
42 value &= (std::uint64_t{1} << width) - 1;
43 const std::size_t bit = index * width;
44 const unsigned shift = bit % 64;
45 words[bit / 64] |= value << shift;
46 if (shift + width > 64) {
47 words[bit / 64 + 1] |= value >> (64 - shift);
48 }
49}
50
59template <StorageImplementation MetadataStorage = AlignedStorage>
60class BlockedMonotoneSamples {
61 public:
62 using metadata_storage_type = MetadataStorage;
63
64 BlockedMonotoneSamples() = default;
65
66 explicit BlockedMonotoneSamples(std::span<const std::uint64_t> values)
68 {
69 if (!std::is_sorted(values.begin(), values.end())) {
70 throw std::invalid_argument("Monotone samples must be non-decreasing");
71 }
72 const std::size_t groups =
73 values.empty() ? 0 : 1 + (values.size() - 1) / 32;
75 std::size_t words = 0;
76 for (std::size_t group = 0; group < groups; ++group) {
77 const std::size_t begin = group * 32;
78 const std::size_t count =
79 std::min<std::size_t>(32, values.size() - begin);
80 const unsigned width =
81 std::bit_width((values[begin + count - 1] - values[begin]) >> 5);
82 offsets_[group] = words | (std::uint64_t{width} << 58);
83 const std::size_t record_words = 2 + (count * width + 63) / 64;
84 if (words > std::numeric_limits<std::size_t>::max() / 64 - record_words) {
85 throw std::length_error("Monotone samples are too large");
86 }
87 words += record_words;
88 }
90 auto* packed = storage_.data();
91 for (std::size_t group = 0; group < groups; ++group) {
92 const std::size_t begin = group * 32;
93 const std::size_t count =
94 std::min<std::size_t>(32, values.size() - begin);
95 const auto descriptor = offsets_[group];
96 auto* record = packed + (descriptor & kOffsetMask);
97 record[0] = values[begin];
98 const unsigned width = descriptor >> 58;
99 for (std::size_t i = 0; i < count; ++i) {
100 const std::uint64_t delta = values[begin + i] - record[0];
101 record[1] |= std::uint64_t{1} << ((delta >> width) + i);
102 write_monotone_low(record + 2, i, width, delta);
103 }
104 }
105 }
106
107 std::uint64_t operator[](std::size_t index) const {
108 const std::uint64_t descriptor = offsets_[index / 32];
109 const auto* record = storage_.data() + (descriptor & kOffsetMask);
110 const std::size_t local = index % 32;
111 const unsigned width = descriptor >> 58;
112 const std::uint64_t high = ::select_64(record[1], local) - local;
113 return record[0] + (high << width) +
114 read_monotone_low(record + 2, local, width);
115 }
116
117 std::size_t allocated_bytes() const {
118 return storage_.allocated_bytes() + offsets_.allocated_bytes();
119 }
120
121 protected:
122 static constexpr std::uint64_t kOffsetMask = (std::uint64_t{1} << 58) - 1;
125};
126
128 std::uint64_t begin = 0;
129 std::uint64_t mask = 0;
130};
131
132// An empty key list allocates no bitmap. Callers only query the map for mixed
133// shapes; homogeneous, narrow and wide-prefix shapes retain their shortcuts.
134inline std::vector<SampleWideGroup> make_sample_wide_groups(
135 std::size_t count,
136 std::span<const std::uint64_t> keys) {
137 if (keys.empty()) {
138 return {};
139 }
140 std::vector<SampleWideGroup> groups(count == 0 ? 0 : 1 + (count - 1) / 32);
141 std::size_t next = 0;
142 for (std::size_t g = 0; g < groups.size(); ++g) {
143 groups[g].begin = next;
144 while (next < keys.size() && keys[next] / 32 == g) {
145 assert(keys[next] < count);
146 assert(next == 0 || keys[next - 1] < keys[next]);
147 groups[g].mask |= std::uint64_t{1} << (keys[next] % 32);
148 ++next;
149 }
150 }
151 assert(next == keys.size());
152 return groups;
153}
154
155inline std::optional<std::size_t> sample_wide_entry(std::uint64_t begin,
156 std::uint64_t mask,
157 unsigned local) {
158 const auto bit = std::uint64_t{1} << local;
159 if (!(mask & bit)) {
160 return std::nullopt;
161 }
162 return begin + std::popcount(mask & (bit - 1));
163}
164
166template <class Base>
167class BitmapMonotoneSamples : public Base {
168 public:
169 BitmapMonotoneSamples() = default;
170 BitmapMonotoneSamples(std::span<const std::uint64_t> values,
171 std::span<const std::uint64_t> wide = {})
172 : Base(values),
173 wide_(std::span<const SampleWideGroup>(
174 make_sample_wide_groups(values.size(), wide))) {}
175
176 std::optional<std::size_t> wide_entry(std::size_t index) const {
177 if (wide_.empty()) {
178 return std::nullopt;
179 }
180 const auto& group = wide_[index / 32];
181 return sample_wide_entry(group.begin, group.mask, index % 32);
182 }
183 std::size_t allocated_bytes() const {
184 return Base::allocated_bytes() + wide_.allocated_bytes();
185 }
186
187 private:
189};
190
191} // namespace pixie::detail::compressed
Definition metadata_array.h:27