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 /// How many bytes of memory this representation is holding.
25 ///
26 /// The two cheap arms hold none at all, which is the point of having them.
27 #[must_use]
28 pub fn footprint(&self) -> usize {
29 match self {
30 Self::AllValid | Self::AllInvalid => 0,
31 Self::Mask(mask) => mask.footprint(),
32 }
33 }
34
35 /// Whether the value at `index` is not null.
36 ///
37 /// Out of range reads report invalid rather than panicking, because this is called from
38 /// kernels that are allowed to read past the end of a partially filled vector.
39 #[must_use]
40 pub fn is_valid(&self, index: usize) -> bool {
41 match self {
42 Self::AllValid => true,
43 Self::AllInvalid => false,
44 Self::Mask(mask) => mask.get(index),
45 }
46 }
47
48 /// Whether any value in the first `len` is null.
49 #[must_use]
50 pub fn has_nulls(&self, len: usize) -> bool {
51 match self {
52 Self::AllValid => false,
53 Self::AllInvalid => len > 0,
54 Self::Mask(mask) => mask.count_valid(len) != len,
55 }
56 }
57
58 /// How many of the first `len` values are not null.
59 #[must_use]
60 pub fn count_valid(&self, len: usize) -> usize {
61 match self {
62 Self::AllValid => len,
63 Self::AllInvalid => 0,
64 Self::Mask(mask) => mask.count_valid(len),
65 }
66 }
67
68 /// Collapses a mask that turned out to be uniform back to one of the flag cases.
69 ///
70 /// Worth doing at the end of any operation that builds a mask, because every kernel
71 /// downstream then gets to take the branch it wants rather than walking a bitmap to find out
72 /// what it already could have been told.
73 #[must_use]
74 pub fn normalize(self, len: usize) -> Self {
75 match self {
76 Self::Mask(ref mask) => {
77 let valid = mask.count_valid(len);
78 if valid == len {
79 Self::AllValid
80 } else if valid == 0 {
81 Self::AllInvalid
82 } else {
83 self
84 }
85 }
86 other => other,
87 }
88 }
89
90 /// The validity of a vector where `index` has just been made null.
91 ///
92 /// Takes and returns by value because setting a null on an `AllValid` vector has to
93 /// materialize a mask, and hiding that behind `&mut self` hides an allocation.
94 #[must_use]
95 pub fn with_null(self, index: usize, len: usize) -> Self {
96 let mut mask = match self {
97 Self::AllValid => Bitmap::all_valid(len),
98 Self::AllInvalid => return Self::AllInvalid,
99 Self::Mask(mask) => mask,
100 };
101 mask.set(index, false);
102 Self::Mask(mask)
103 }
104
105 /// Validity built from a per-value predicate, normalized.
106 pub fn from_iter(len: usize, valid: impl Fn(usize) -> bool) -> Self {
107 let mut mask = Bitmap::all_valid(len);
108 for index in 0..len {
109 if !valid(index) {
110 mask.set(index, false);
111 }
112 }
113 Self::Mask(mask).normalize(len)
114 }
115
116 /// Validity packed from one byte a row, which is what a kernel that accumulated its answer in a
117 /// `Vec<bool>` is holding when it finishes.
118 ///
119 /// The difference from [`Self::from_iter`] is the shape rather than the answer. `from_iter`
120 /// calls a closure and then a read modify write on a byte of the bitmap, once per row, and the
121 /// read modify write is a dependency on the row before it. This reads sixty four bytes and
122 /// writes one word, which has no dependency in it at all and is what the compiler needs to see
123 /// before it will use a vector instruction. On a thousand row vector that is the difference
124 /// between two nanoseconds a row and something too small to measure.
125 /// The bits past the end of the last word are set rather than clear, which looks like a detail
126 /// and is not. [`Bitmap`] does not carry a length, so its equality is over whole words, and
127 /// [`Bitmap::all_valid`] leaves those bits set. A constructor that left them clear would build
128 /// a validity that says exactly the same thing about every row that exists and still compares
129 /// unequal to the one [`Self::from_iter`] builds, which is a test failure with no wrong answer
130 /// in it and an afternoon to work out.
131 #[must_use]
132 pub fn from_run(valid: &[bool]) -> Self {
133 let len = valid.len();
134 let mut words = vec![0u64; len.div_ceil(64)];
135 for (word, run) in words.iter_mut().zip(valid.chunks(64)) {
136 // Only the last run can be short, and the shift is written around rather than as
137 // `u64::MAX << 64`, which is not a shift this machine has.
138 let mut packed = if run.len() == 64 { 0 } else { u64::MAX << run.len() };
139 for (bit, &live) in run.iter().enumerate() {
140 packed |= u64::from(live) << bit;
141 }
142 *word = packed;
143 }
144 Self::Mask(Bitmap { words }).normalize(len)
145 }
146
147 /// The validity of `len` rows starting at `at`.
148 ///
149 /// The two flag arms are the point. A cut of a column with no nulls in it has no nulls in it,
150 /// and answering that with a flag rather than by building a mask and collapsing it again is the
151 /// difference between a cut costing nothing and costing a pass over the rows. Every column of
152 /// `hits` that is not nullable takes this, and a page is cut into chunk sized pieces, so it is
153 /// taken once per chunk per column of every scan.
154 #[must_use]
155 pub fn slice(&self, at: usize, len: usize) -> Self {
156 match self {
157 Self::AllValid => Self::AllValid,
158 Self::AllInvalid => Self::AllInvalid,
159 Self::Mask(mask) => Self::Mask(mask.slice(at, len)).normalize(len),
160 }
161 }
162
163 /// The validity of a value that is valid in both inputs, which is what almost every binary
164 /// operator wants and is worth having in one place.
165 #[must_use]
166 pub fn and(&self, other: &Self, len: usize) -> Self {
167 match (self, other) {
168 (Self::AllInvalid, _) | (_, Self::AllInvalid) => Self::AllInvalid,
169 (Self::AllValid, Self::AllValid) => Self::AllValid,
170 (Self::AllValid, right) => right.clone().normalize(len),
171 (left, Self::AllValid) => left.clone().normalize(len),
172 (Self::Mask(left), Self::Mask(right)) => {
173 let mut result = left.clone();
174 result.and_with(right);
175 Self::Mask(result).normalize(len)
176 }
177 }
178 }
179}
180
181/// One bit per value, set meaning valid.
182///
183/// Words are `u64` because that is the width the popcount and the mask tests want, and because a
184/// 1024 value vector is exactly 16 of them, which fits in a quarter of a cache line pair and is
185/// the reason the vector size is 1024 rather than DuckDB's 2048.
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct Bitmap {
188 words: Vec<u64>,
189}
190
191impl Bitmap {
192 /// How many bytes of memory this bitmap is holding.
193 #[must_use]
194 pub fn footprint(&self) -> usize {
195 self.words.capacity() * size_of::<u64>()
196 }
197
198 /// A bitmap with room for `len` values, all valid.
199 #[must_use]
200 pub fn all_valid(len: usize) -> Self {
201 Self { words: vec![u64::MAX; len.div_ceil(64)] }
202 }
203
204 /// A bitmap with room for `len` values, all null.
205 #[must_use]
206 pub fn all_invalid(len: usize) -> Self {
207 Self { words: vec![0; len.div_ceil(64)] }
208 }
209
210 /// Whether the value at `index` is valid. Past the end reads as invalid.
211 #[must_use]
212 pub fn get(&self, index: usize) -> bool {
213 let word = index / 64;
214 self.words.get(word).is_some_and(|w| w >> (index % 64) & 1 == 1)
215 }
216
217 /// Sets whether the value at `index` is valid, growing the bitmap if it has to.
218 pub fn set(&mut self, index: usize, valid: bool) {
219 let word = index / 64;
220 if word >= self.words.len() {
221 self.words.resize(word + 1, 0);
222 }
223 let bit = 1u64 << (index % 64);
224 if valid {
225 self.words[word] |= bit;
226 } else {
227 self.words[word] &= !bit;
228 }
229 }
230
231 /// How many of the first `len` values are valid.
232 #[must_use]
233 pub fn count_valid(&self, len: usize) -> usize {
234 let mut count = 0usize;
235 let full_words = len / 64;
236 for word in self.words.iter().take(full_words) {
237 count += word.count_ones() as usize;
238 }
239 let tail = len % 64;
240 if tail > 0 {
241 // A let chain would read better here, but let chains want Rust 1.88 and the declared
242 // minimum in the manifest is 1.85.0. Written the long way rather than moving the
243 // minimum, since nothing about this needs a newer compiler.
244 if let Some(word) = self.words.get(full_words) {
245 // Mask off the bits past the end, which are whatever the last resize left there.
246 let keep = u64::MAX >> (64 - tail);
247 count += (word & keep).count_ones() as usize;
248 }
249 }
250 count
251 }
252
253 /// Sixty four validity bits at once, the lowest numbered row in the lowest bit.
254 ///
255 /// Past the end reads as all null, which is the same answer [`Self::get`] gives one bit at a
256 /// time. This exists because a kernel that asks [`Self::get`] once per row pays a bounds check,
257 /// a divide and a shift for each of them, and the word it wants was already in a register for
258 /// the previous sixty three. A loop that reads the word once and walks its bits is the same
259 /// answer at a fraction of the cost, and the three call sites that do that are the difference
260 /// between a nullable column being free and being the slowest thing in the kernel.
261 #[must_use]
262 pub fn word(&self, at: usize) -> u64 {
263 self.words.get(at).copied().unwrap_or(0)
264 }
265
266 /// The `len` bits starting at `at`, moved down to start at bit zero.
267 ///
268 /// A word at a time, because a cut is almost never on a word boundary and doing it a bit at a
269 /// time is a divide, a shift and a read modify write per row. Each output word is the high part
270 /// of one input word and the low part of the next, which is two loads and three shifts for
271 /// sixty four rows.
272 ///
273 /// The bits past `len` in the last word are set rather than clear, for the reason
274 /// [`Validity::from_run`] gives: this type has no length, so its equality is over whole words
275 /// and a constructor that left them clear would compare unequal to one that did not.
276 #[must_use]
277 pub fn slice(&self, at: usize, len: usize) -> Self {
278 let skip = at / 64;
279 let shift = (at % 64) as u32;
280 let mut words = Vec::with_capacity(len.div_ceil(64));
281 for index in 0..len.div_ceil(64) {
282 let low = self.word(skip + index) >> shift;
283 // Written around rather than as a shift by sixty four, which is not a shift this
284 // machine has, and when the cut is word aligned there is no next word to take from.
285 let high = if shift == 0 { 0 } else { self.word(skip + index + 1) << (64 - shift) };
286 words.push(low | high);
287 }
288 if let Some(last) = words.last_mut() {
289 let used = len % 64;
290 if used != 0 {
291 *last |= u64::MAX << used;
292 }
293 }
294 Self { words }
295 }
296
297 /// Intersects this bitmap with another, in place.
298 pub fn and_with(&mut self, other: &Self) {
299 for (index, word) in self.words.iter_mut().enumerate() {
300 *word &= other.words.get(index).copied().unwrap_or(0);
301 }
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::{Bitmap, Validity};
308
309 #[test]
310 fn the_three_cases_answer_the_same_question_the_same_way() {
311 let mut mask = Bitmap::all_valid(8);
312 assert!(Validity::AllValid.is_valid(3));
313 assert!(!Validity::AllInvalid.is_valid(3));
314 assert!(Validity::Mask(mask.clone()).is_valid(3));
315 mask.set(3, false);
316 assert!(!Validity::Mask(mask).is_valid(3));
317 }
318
319 #[test]
320 fn a_cut_of_a_mask_says_what_reading_it_a_bit_at_a_time_says() {
321 // Every start and every length over a pattern with no period in common with sixty four, so
322 // that the word boundary lands in a different place in the pattern for every cut. The word
323 // at a time cut and the bit at a time one have to agree bit for bit, including the bits
324 // past the end of the last word, since this type compares by whole words.
325 let rows = 200;
326 let mut mask = Bitmap::all_valid(rows);
327 // row at a time: building the pattern the test reads, not a path anything runs.
328 for row in 0..rows {
329 mask.set(row, row % 7 != 0 && row % 11 != 3);
330 }
331 let whole = Validity::Mask(mask.clone());
332 for at in 0..70 {
333 for len in 0..70 {
334 let wanted = Validity::from_iter(len, |row| whole.is_valid(at + row));
335 assert_eq!(whole.slice(at, len), wanted, "rows {at} to {}", at + len);
336 }
337 }
338 }
339
340 #[test]
341 fn a_cut_of_a_column_with_no_nulls_has_no_nulls_and_no_mask() {
342 assert_eq!(Validity::AllValid.slice(17, 33), Validity::AllValid);
343 assert_eq!(Validity::AllInvalid.slice(17, 33), Validity::AllInvalid);
344 // And a cut of a mask that happens to be uniform over the range collapses the same way.
345 let mut mask = Bitmap::all_valid(128);
346 mask.set(100, false);
347 assert_eq!(Validity::Mask(mask).slice(0, 64), Validity::AllValid);
348 }
349
350 #[test]
351 fn a_uniform_mask_collapses_to_the_flag_it_should_have_been() {
352 // The point of doing this at the end of every operation that builds a mask: the kernel
353 // downstream gets to branch once rather than walk a bitmap to learn what it was told.
354 assert_eq!(Validity::Mask(Bitmap::all_valid(64)).normalize(64), Validity::AllValid);
355 assert_eq!(Validity::Mask(Bitmap::all_invalid(64)).normalize(64), Validity::AllInvalid);
356 let mut mask = Bitmap::all_valid(64);
357 mask.set(7, false);
358 assert!(matches!(Validity::Mask(mask).normalize(64), Validity::Mask(_)));
359 }
360
361 #[test]
362 fn a_word_of_validity_says_the_same_thing_the_bits_do_one_at_a_time() {
363 let mut mask = Bitmap::all_valid(200);
364 mask.set(0, false);
365 mask.set(63, false);
366 mask.set(64, false);
367 mask.set(199, false);
368 for index in 0..200 {
369 let from_word = mask.word(index / 64) >> (index % 64) & 1 == 1;
370 assert_eq!(from_word, mask.get(index), "{index}");
371 }
372 // Past the end is all null, which is what reading one bit past the end says too.
373 assert_eq!(mask.word(9), 0);
374 assert!(!mask.get(9 * 64));
375 }
376
377 #[test]
378 fn packing_a_run_of_bytes_says_the_same_thing_as_setting_the_bits() {
379 // Two lengths that are not a whole number of words, because the bits past the end of the
380 // last word are the part of this that is easy to get wrong.
381 for len in [0, 1, 63, 64, 65, 100, 1024] {
382 let live: Vec<bool> = (0..len).map(|index| index % 7 != 0).collect();
383 let packed = Validity::from_run(&live);
384 let set = Validity::from_iter(len, |index| live[index]);
385 assert_eq!(packed, set, "{len}");
386 for (index, &want) in live.iter().enumerate() {
387 assert_eq!(packed.is_valid(index), want, "{len} at {index}");
388 }
389 }
390 // The bits past the end of the last word have to match what every other constructor
391 // leaves there, because a bitmap does not carry a length and its equality is over whole
392 // words. This is the assertion that caught it.
393 assert_eq!(
394 Validity::from_run(&[true, false, true]),
395 Validity::from_iter(3, |index| index != 1)
396 );
397 // And it collapses the uniform cases the same way everything else does.
398 assert_eq!(Validity::from_run(&[true; 64]), Validity::AllValid);
399 assert_eq!(Validity::from_run(&[false; 64]), Validity::AllInvalid);
400 assert_eq!(Validity::from_run(&[]), Validity::AllValid);
401 }
402
403 #[test]
404 fn counting_stops_at_the_length_and_not_at_the_word_boundary() {
405 // A 1024 vector is 16 words exactly, but a partially filled one is not, and the bits past
406 // the end are whatever the last resize left there. Getting this wrong makes a count that
407 // is right in tests of length 64 and wrong on real data.
408 let mask = Bitmap::all_valid(100);
409 assert_eq!(mask.count_valid(100), 100);
410 assert_eq!(mask.count_valid(65), 65);
411 assert_eq!(mask.count_valid(1), 1);
412 assert_eq!(mask.count_valid(0), 0);
413 }
414
415 #[test]
416 fn setting_a_null_on_an_all_valid_vector_materializes_a_mask() {
417 let validity = Validity::AllValid.with_null(5, 64);
418 assert!(!validity.is_valid(5));
419 assert!(validity.is_valid(4));
420 assert_eq!(validity.count_valid(64), 63);
421 assert!(validity.has_nulls(64));
422 }
423
424 #[test]
425 fn setting_a_null_on_an_all_invalid_vector_changes_nothing() {
426 assert_eq!(Validity::AllInvalid.with_null(5, 64), Validity::AllInvalid);
427 }
428
429 #[test]
430 fn intersection_short_circuits_on_the_flags() {
431 let mut left = Bitmap::all_valid(8);
432 left.set(0, false);
433 let mut right = Bitmap::all_valid(8);
434 right.set(1, false);
435 let both = Validity::Mask(left.clone()).and(&Validity::Mask(right), 8);
436 assert!(!both.is_valid(0));
437 assert!(!both.is_valid(1));
438 assert!(both.is_valid(2));
439 assert_eq!(both.count_valid(8), 6);
440
441 assert_eq!(Validity::AllValid.and(&Validity::AllValid, 8), Validity::AllValid);
442 assert_eq!(Validity::AllInvalid.and(&Validity::Mask(left), 8), Validity::AllInvalid);
443 }
444
445 #[test]
446 fn validity_from_a_predicate_normalizes_itself() {
447 assert_eq!(Validity::from_iter(16, |_| true), Validity::AllValid);
448 assert_eq!(Validity::from_iter(16, |_| false), Validity::AllInvalid);
449 let mixed = Validity::from_iter(16, |i| i % 2 == 0);
450 assert_eq!(mixed.count_valid(16), 8);
451 }
452}