rudb_vector/validity.rs
1//! Which values in a vector are not null.
2//!
3//! `spec/07-execution.md` section 7.1: validity has three representations and the distinction is
4//! load-bearing. All valid is the absence of a mask and gets the fastest kernels. All invalid is a
5//! flag and short circuits entirely. Anything else is a bitmap.
6//!
7//! Photon's published result is that separate no-null kernels are worth a measurable amount on
8//! real data, because real data is mostly not null. The cost of knowing which case you are in is
9//! one branch per vector rather than one per value, which is why the three cases are an enum here
10//! rather than a bitmap that happens to be all ones.
11
12/// Which values in a vector are valid.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Validity {
15 /// Nothing is null. No mask is stored and the kernels that read this take the fast path.
16 AllValid,
17 /// Everything is null. Most operators can answer without looking at the data at all.
18 AllInvalid,
19 /// Some of each, one bit per value, set meaning valid.
20 Mask(Bitmap),
21}
22
23impl Validity {
24 /// Whether the value at `index` is not null.
25 ///
26 /// Out of range reads report invalid rather than panicking, because this is called from
27 /// kernels that are allowed to read past the end of a partially filled vector.
28 #[must_use]
29 pub fn is_valid(&self, index: usize) -> bool {
30 match self {
31 Self::AllValid => true,
32 Self::AllInvalid => false,
33 Self::Mask(mask) => mask.get(index),
34 }
35 }
36
37 /// Whether any value in the first `len` is null.
38 #[must_use]
39 pub fn has_nulls(&self, len: usize) -> bool {
40 match self {
41 Self::AllValid => false,
42 Self::AllInvalid => len > 0,
43 Self::Mask(mask) => mask.count_valid(len) != len,
44 }
45 }
46
47 /// How many of the first `len` values are not null.
48 #[must_use]
49 pub fn count_valid(&self, len: usize) -> usize {
50 match self {
51 Self::AllValid => len,
52 Self::AllInvalid => 0,
53 Self::Mask(mask) => mask.count_valid(len),
54 }
55 }
56
57 /// Collapses a mask that turned out to be uniform back to one of the flag cases.
58 ///
59 /// Worth doing at the end of any operation that builds a mask, because every kernel
60 /// downstream then gets to take the branch it wants rather than walking a bitmap to find out
61 /// what it already could have been told.
62 #[must_use]
63 pub fn normalize(self, len: usize) -> Self {
64 match self {
65 Self::Mask(ref mask) => {
66 let valid = mask.count_valid(len);
67 if valid == len {
68 Self::AllValid
69 } else if valid == 0 {
70 Self::AllInvalid
71 } else {
72 self
73 }
74 }
75 other => other,
76 }
77 }
78
79 /// The validity of a vector where `index` has just been made null.
80 ///
81 /// Takes and returns by value because setting a null on an `AllValid` vector has to
82 /// materialize a mask, and hiding that behind `&mut self` hides an allocation.
83 #[must_use]
84 pub fn with_null(self, index: usize, len: usize) -> Self {
85 let mut mask = match self {
86 Self::AllValid => Bitmap::all_valid(len),
87 Self::AllInvalid => return Self::AllInvalid,
88 Self::Mask(mask) => mask,
89 };
90 mask.set(index, false);
91 Self::Mask(mask)
92 }
93
94 /// Validity built from a per-value predicate, normalized.
95 pub fn from_iter(len: usize, valid: impl Fn(usize) -> bool) -> Self {
96 let mut mask = Bitmap::all_valid(len);
97 for index in 0..len {
98 if !valid(index) {
99 mask.set(index, false);
100 }
101 }
102 Self::Mask(mask).normalize(len)
103 }
104
105 /// Validity packed from one byte a row, which is what a kernel that accumulated its answer in a
106 /// `Vec<bool>` is holding when it finishes.
107 ///
108 /// The difference from [`Self::from_iter`] is the shape rather than the answer. `from_iter`
109 /// calls a closure and then a read modify write on a byte of the bitmap, once per row, and the
110 /// read modify write is a dependency on the row before it. This reads sixty four bytes and
111 /// writes one word, which has no dependency in it at all and is what the compiler needs to see
112 /// before it will use a vector instruction. On a thousand row vector that is the difference
113 /// between two nanoseconds a row and something too small to measure.
114 /// The bits past the end of the last word are set rather than clear, which looks like a detail
115 /// and is not. [`Bitmap`] does not carry a length, so its equality is over whole words, and
116 /// [`Bitmap::all_valid`] leaves those bits set. A constructor that left them clear would build
117 /// a validity that says exactly the same thing about every row that exists and still compares
118 /// unequal to the one [`Self::from_iter`] builds, which is a test failure with no wrong answer
119 /// in it and an afternoon to work out.
120 #[must_use]
121 pub fn from_run(valid: &[bool]) -> Self {
122 let len = valid.len();
123 let mut words = vec![0u64; len.div_ceil(64)];
124 for (word, run) in words.iter_mut().zip(valid.chunks(64)) {
125 // Only the last run can be short, and the shift is written around rather than as
126 // `u64::MAX << 64`, which is not a shift this machine has.
127 let mut packed = if run.len() == 64 { 0 } else { u64::MAX << run.len() };
128 for (bit, &live) in run.iter().enumerate() {
129 packed |= u64::from(live) << bit;
130 }
131 *word = packed;
132 }
133 Self::Mask(Bitmap { words }).normalize(len)
134 }
135
136 /// The validity of a value that is valid in both inputs, which is what almost every binary
137 /// operator wants and is worth having in one place.
138 #[must_use]
139 pub fn and(&self, other: &Self, len: usize) -> Self {
140 match (self, other) {
141 (Self::AllInvalid, _) | (_, Self::AllInvalid) => Self::AllInvalid,
142 (Self::AllValid, Self::AllValid) => Self::AllValid,
143 (Self::AllValid, right) => right.clone().normalize(len),
144 (left, Self::AllValid) => left.clone().normalize(len),
145 (Self::Mask(left), Self::Mask(right)) => {
146 let mut result = left.clone();
147 result.and_with(right);
148 Self::Mask(result).normalize(len)
149 }
150 }
151 }
152}
153
154/// One bit per value, set meaning valid.
155///
156/// Words are `u64` because that is the width the popcount and the mask tests want, and because a
157/// 1024 value vector is exactly 16 of them, which fits in a quarter of a cache line pair and is
158/// the reason the vector size is 1024 rather than DuckDB's 2048.
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct Bitmap {
161 words: Vec<u64>,
162}
163
164impl Bitmap {
165 /// A bitmap with room for `len` values, all valid.
166 #[must_use]
167 pub fn all_valid(len: usize) -> Self {
168 Self { words: vec![u64::MAX; len.div_ceil(64)] }
169 }
170
171 /// A bitmap with room for `len` values, all null.
172 #[must_use]
173 pub fn all_invalid(len: usize) -> Self {
174 Self { words: vec![0; len.div_ceil(64)] }
175 }
176
177 /// Whether the value at `index` is valid. Past the end reads as invalid.
178 #[must_use]
179 pub fn get(&self, index: usize) -> bool {
180 let word = index / 64;
181 self.words.get(word).is_some_and(|w| w >> (index % 64) & 1 == 1)
182 }
183
184 /// Sets whether the value at `index` is valid, growing the bitmap if it has to.
185 pub fn set(&mut self, index: usize, valid: bool) {
186 let word = index / 64;
187 if word >= self.words.len() {
188 self.words.resize(word + 1, 0);
189 }
190 let bit = 1u64 << (index % 64);
191 if valid {
192 self.words[word] |= bit;
193 } else {
194 self.words[word] &= !bit;
195 }
196 }
197
198 /// How many of the first `len` values are valid.
199 #[must_use]
200 pub fn count_valid(&self, len: usize) -> usize {
201 let mut count = 0usize;
202 let full_words = len / 64;
203 for word in self.words.iter().take(full_words) {
204 count += word.count_ones() as usize;
205 }
206 let tail = len % 64;
207 if tail > 0 {
208 // A let chain would read better here, but let chains want Rust 1.88 and the declared
209 // minimum in the manifest is 1.85.0. Written the long way rather than moving the
210 // minimum, since nothing about this needs a newer compiler.
211 if let Some(word) = self.words.get(full_words) {
212 // Mask off the bits past the end, which are whatever the last resize left there.
213 let keep = u64::MAX >> (64 - tail);
214 count += (word & keep).count_ones() as usize;
215 }
216 }
217 count
218 }
219
220 /// Sixty four validity bits at once, the lowest numbered row in the lowest bit.
221 ///
222 /// Past the end reads as all null, which is the same answer [`Self::get`] gives one bit at a
223 /// time. This exists because a kernel that asks [`Self::get`] once per row pays a bounds check,
224 /// a divide and a shift for each of them, and the word it wants was already in a register for
225 /// the previous sixty three. A loop that reads the word once and walks its bits is the same
226 /// answer at a fraction of the cost, and the three call sites that do that are the difference
227 /// between a nullable column being free and being the slowest thing in the kernel.
228 #[must_use]
229 pub fn word(&self, at: usize) -> u64 {
230 self.words.get(at).copied().unwrap_or(0)
231 }
232
233 /// Intersects this bitmap with another, in place.
234 pub fn and_with(&mut self, other: &Self) {
235 for (index, word) in self.words.iter_mut().enumerate() {
236 *word &= other.words.get(index).copied().unwrap_or(0);
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::{Bitmap, Validity};
244
245 #[test]
246 fn the_three_cases_answer_the_same_question_the_same_way() {
247 let mut mask = Bitmap::all_valid(8);
248 assert!(Validity::AllValid.is_valid(3));
249 assert!(!Validity::AllInvalid.is_valid(3));
250 assert!(Validity::Mask(mask.clone()).is_valid(3));
251 mask.set(3, false);
252 assert!(!Validity::Mask(mask).is_valid(3));
253 }
254
255 #[test]
256 fn a_uniform_mask_collapses_to_the_flag_it_should_have_been() {
257 // The point of doing this at the end of every operation that builds a mask: the kernel
258 // downstream gets to branch once rather than walk a bitmap to learn what it was told.
259 assert_eq!(Validity::Mask(Bitmap::all_valid(64)).normalize(64), Validity::AllValid);
260 assert_eq!(Validity::Mask(Bitmap::all_invalid(64)).normalize(64), Validity::AllInvalid);
261 let mut mask = Bitmap::all_valid(64);
262 mask.set(7, false);
263 assert!(matches!(Validity::Mask(mask).normalize(64), Validity::Mask(_)));
264 }
265
266 #[test]
267 fn a_word_of_validity_says_the_same_thing_the_bits_do_one_at_a_time() {
268 let mut mask = Bitmap::all_valid(200);
269 mask.set(0, false);
270 mask.set(63, false);
271 mask.set(64, false);
272 mask.set(199, false);
273 for index in 0..200 {
274 let from_word = mask.word(index / 64) >> (index % 64) & 1 == 1;
275 assert_eq!(from_word, mask.get(index), "{index}");
276 }
277 // Past the end is all null, which is what reading one bit past the end says too.
278 assert_eq!(mask.word(9), 0);
279 assert!(!mask.get(9 * 64));
280 }
281
282 #[test]
283 fn packing_a_run_of_bytes_says_the_same_thing_as_setting_the_bits() {
284 // Two lengths that are not a whole number of words, because the bits past the end of the
285 // last word are the part of this that is easy to get wrong.
286 for len in [0, 1, 63, 64, 65, 100, 1024] {
287 let live: Vec<bool> = (0..len).map(|index| index % 7 != 0).collect();
288 let packed = Validity::from_run(&live);
289 let set = Validity::from_iter(len, |index| live[index]);
290 assert_eq!(packed, set, "{len}");
291 for (index, &want) in live.iter().enumerate() {
292 assert_eq!(packed.is_valid(index), want, "{len} at {index}");
293 }
294 }
295 // The bits past the end of the last word have to match what every other constructor
296 // leaves there, because a bitmap does not carry a length and its equality is over whole
297 // words. This is the assertion that caught it.
298 assert_eq!(
299 Validity::from_run(&[true, false, true]),
300 Validity::from_iter(3, |index| index != 1)
301 );
302 // And it collapses the uniform cases the same way everything else does.
303 assert_eq!(Validity::from_run(&[true; 64]), Validity::AllValid);
304 assert_eq!(Validity::from_run(&[false; 64]), Validity::AllInvalid);
305 assert_eq!(Validity::from_run(&[]), Validity::AllValid);
306 }
307
308 #[test]
309 fn counting_stops_at_the_length_and_not_at_the_word_boundary() {
310 // A 1024 vector is 16 words exactly, but a partially filled one is not, and the bits past
311 // the end are whatever the last resize left there. Getting this wrong makes a count that
312 // is right in tests of length 64 and wrong on real data.
313 let mask = Bitmap::all_valid(100);
314 assert_eq!(mask.count_valid(100), 100);
315 assert_eq!(mask.count_valid(65), 65);
316 assert_eq!(mask.count_valid(1), 1);
317 assert_eq!(mask.count_valid(0), 0);
318 }
319
320 #[test]
321 fn setting_a_null_on_an_all_valid_vector_materializes_a_mask() {
322 let validity = Validity::AllValid.with_null(5, 64);
323 assert!(!validity.is_valid(5));
324 assert!(validity.is_valid(4));
325 assert_eq!(validity.count_valid(64), 63);
326 assert!(validity.has_nulls(64));
327 }
328
329 #[test]
330 fn setting_a_null_on_an_all_invalid_vector_changes_nothing() {
331 assert_eq!(Validity::AllInvalid.with_null(5, 64), Validity::AllInvalid);
332 }
333
334 #[test]
335 fn intersection_short_circuits_on_the_flags() {
336 let mut left = Bitmap::all_valid(8);
337 left.set(0, false);
338 let mut right = Bitmap::all_valid(8);
339 right.set(1, false);
340 let both = Validity::Mask(left.clone()).and(&Validity::Mask(right), 8);
341 assert!(!both.is_valid(0));
342 assert!(!both.is_valid(1));
343 assert!(both.is_valid(2));
344 assert_eq!(both.count_valid(8), 6);
345
346 assert_eq!(Validity::AllValid.and(&Validity::AllValid, 8), Validity::AllValid);
347 assert_eq!(Validity::AllInvalid.and(&Validity::Mask(left), 8), Validity::AllInvalid);
348 }
349
350 #[test]
351 fn validity_from_a_predicate_normalizes_itself() {
352 assert_eq!(Validity::from_iter(16, |_| true), Validity::AllValid);
353 assert_eq!(Validity::from_iter(16, |_| false), Validity::AllInvalid);
354 let mixed = Validity::from_iter(16, |i| i % 2 == 0);
355 assert_eq!(mixed.count_valid(16), 8);
356 }
357}