Skip to main content

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