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
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 a value that is valid in both inputs, which is what almost every binary
148    /// operator wants and is worth having in one place.
149    #[must_use]
150    pub fn and(&self, other: &Self, len: usize) -> Self {
151        match (self, other) {
152            (Self::AllInvalid, _) | (_, Self::AllInvalid) => Self::AllInvalid,
153            (Self::AllValid, Self::AllValid) => Self::AllValid,
154            (Self::AllValid, right) => right.clone().normalize(len),
155            (left, Self::AllValid) => left.clone().normalize(len),
156            (Self::Mask(left), Self::Mask(right)) => {
157                let mut result = left.clone();
158                result.and_with(right);
159                Self::Mask(result).normalize(len)
160            }
161        }
162    }
163}
164
165/// One bit per value, set meaning valid.
166///
167/// Words are `u64` because that is the width the popcount and the mask tests want, and because a
168/// 1024 value vector is exactly 16 of them, which fits in a quarter of a cache line pair and is
169/// the reason the vector size is 1024 rather than DuckDB's 2048.
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct Bitmap {
172    words: Vec<u64>,
173}
174
175impl Bitmap {
176    /// How many bytes of memory this bitmap is holding.
177    #[must_use]
178    pub fn footprint(&self) -> usize {
179        self.words.capacity() * size_of::<u64>()
180    }
181
182    /// A bitmap with room for `len` values, all valid.
183    #[must_use]
184    pub fn all_valid(len: usize) -> Self {
185        Self { words: vec![u64::MAX; len.div_ceil(64)] }
186    }
187
188    /// A bitmap with room for `len` values, all null.
189    #[must_use]
190    pub fn all_invalid(len: usize) -> Self {
191        Self { words: vec![0; len.div_ceil(64)] }
192    }
193
194    /// Whether the value at `index` is valid. Past the end reads as invalid.
195    #[must_use]
196    pub fn get(&self, index: usize) -> bool {
197        let word = index / 64;
198        self.words.get(word).is_some_and(|w| w >> (index % 64) & 1 == 1)
199    }
200
201    /// Sets whether the value at `index` is valid, growing the bitmap if it has to.
202    pub fn set(&mut self, index: usize, valid: bool) {
203        let word = index / 64;
204        if word >= self.words.len() {
205            self.words.resize(word + 1, 0);
206        }
207        let bit = 1u64 << (index % 64);
208        if valid {
209            self.words[word] |= bit;
210        } else {
211            self.words[word] &= !bit;
212        }
213    }
214
215    /// How many of the first `len` values are valid.
216    #[must_use]
217    pub fn count_valid(&self, len: usize) -> usize {
218        let mut count = 0usize;
219        let full_words = len / 64;
220        for word in self.words.iter().take(full_words) {
221            count += word.count_ones() as usize;
222        }
223        let tail = len % 64;
224        if tail > 0 {
225            // A let chain would read better here, but let chains want Rust 1.88 and the declared
226            // minimum in the manifest is 1.85.0. Written the long way rather than moving the
227            // minimum, since nothing about this needs a newer compiler.
228            if let Some(word) = self.words.get(full_words) {
229                // Mask off the bits past the end, which are whatever the last resize left there.
230                let keep = u64::MAX >> (64 - tail);
231                count += (word & keep).count_ones() as usize;
232            }
233        }
234        count
235    }
236
237    /// Sixty four validity bits at once, the lowest numbered row in the lowest bit.
238    ///
239    /// Past the end reads as all null, which is the same answer [`Self::get`] gives one bit at a
240    /// time. This exists because a kernel that asks [`Self::get`] once per row pays a bounds check,
241    /// a divide and a shift for each of them, and the word it wants was already in a register for
242    /// the previous sixty three. A loop that reads the word once and walks its bits is the same
243    /// answer at a fraction of the cost, and the three call sites that do that are the difference
244    /// between a nullable column being free and being the slowest thing in the kernel.
245    #[must_use]
246    pub fn word(&self, at: usize) -> u64 {
247        self.words.get(at).copied().unwrap_or(0)
248    }
249
250    /// Intersects this bitmap with another, in place.
251    pub fn and_with(&mut self, other: &Self) {
252        for (index, word) in self.words.iter_mut().enumerate() {
253            *word &= other.words.get(index).copied().unwrap_or(0);
254        }
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::{Bitmap, Validity};
261
262    #[test]
263    fn the_three_cases_answer_the_same_question_the_same_way() {
264        let mut mask = Bitmap::all_valid(8);
265        assert!(Validity::AllValid.is_valid(3));
266        assert!(!Validity::AllInvalid.is_valid(3));
267        assert!(Validity::Mask(mask.clone()).is_valid(3));
268        mask.set(3, false);
269        assert!(!Validity::Mask(mask).is_valid(3));
270    }
271
272    #[test]
273    fn a_uniform_mask_collapses_to_the_flag_it_should_have_been() {
274        // The point of doing this at the end of every operation that builds a mask: the kernel
275        // downstream gets to branch once rather than walk a bitmap to learn what it was told.
276        assert_eq!(Validity::Mask(Bitmap::all_valid(64)).normalize(64), Validity::AllValid);
277        assert_eq!(Validity::Mask(Bitmap::all_invalid(64)).normalize(64), Validity::AllInvalid);
278        let mut mask = Bitmap::all_valid(64);
279        mask.set(7, false);
280        assert!(matches!(Validity::Mask(mask).normalize(64), Validity::Mask(_)));
281    }
282
283    #[test]
284    fn a_word_of_validity_says_the_same_thing_the_bits_do_one_at_a_time() {
285        let mut mask = Bitmap::all_valid(200);
286        mask.set(0, false);
287        mask.set(63, false);
288        mask.set(64, false);
289        mask.set(199, false);
290        for index in 0..200 {
291            let from_word = mask.word(index / 64) >> (index % 64) & 1 == 1;
292            assert_eq!(from_word, mask.get(index), "{index}");
293        }
294        // Past the end is all null, which is what reading one bit past the end says too.
295        assert_eq!(mask.word(9), 0);
296        assert!(!mask.get(9 * 64));
297    }
298
299    #[test]
300    fn packing_a_run_of_bytes_says_the_same_thing_as_setting_the_bits() {
301        // Two lengths that are not a whole number of words, because the bits past the end of the
302        // last word are the part of this that is easy to get wrong.
303        for len in [0, 1, 63, 64, 65, 100, 1024] {
304            let live: Vec<bool> = (0..len).map(|index| index % 7 != 0).collect();
305            let packed = Validity::from_run(&live);
306            let set = Validity::from_iter(len, |index| live[index]);
307            assert_eq!(packed, set, "{len}");
308            for (index, &want) in live.iter().enumerate() {
309                assert_eq!(packed.is_valid(index), want, "{len} at {index}");
310            }
311        }
312        // The bits past the end of the last word have to match what every other constructor
313        // leaves there, because a bitmap does not carry a length and its equality is over whole
314        // words. This is the assertion that caught it.
315        assert_eq!(
316            Validity::from_run(&[true, false, true]),
317            Validity::from_iter(3, |index| index != 1)
318        );
319        // And it collapses the uniform cases the same way everything else does.
320        assert_eq!(Validity::from_run(&[true; 64]), Validity::AllValid);
321        assert_eq!(Validity::from_run(&[false; 64]), Validity::AllInvalid);
322        assert_eq!(Validity::from_run(&[]), Validity::AllValid);
323    }
324
325    #[test]
326    fn counting_stops_at_the_length_and_not_at_the_word_boundary() {
327        // A 1024 vector is 16 words exactly, but a partially filled one is not, and the bits past
328        // the end are whatever the last resize left there. Getting this wrong makes a count that
329        // is right in tests of length 64 and wrong on real data.
330        let mask = Bitmap::all_valid(100);
331        assert_eq!(mask.count_valid(100), 100);
332        assert_eq!(mask.count_valid(65), 65);
333        assert_eq!(mask.count_valid(1), 1);
334        assert_eq!(mask.count_valid(0), 0);
335    }
336
337    #[test]
338    fn setting_a_null_on_an_all_valid_vector_materializes_a_mask() {
339        let validity = Validity::AllValid.with_null(5, 64);
340        assert!(!validity.is_valid(5));
341        assert!(validity.is_valid(4));
342        assert_eq!(validity.count_valid(64), 63);
343        assert!(validity.has_nulls(64));
344    }
345
346    #[test]
347    fn setting_a_null_on_an_all_invalid_vector_changes_nothing() {
348        assert_eq!(Validity::AllInvalid.with_null(5, 64), Validity::AllInvalid);
349    }
350
351    #[test]
352    fn intersection_short_circuits_on_the_flags() {
353        let mut left = Bitmap::all_valid(8);
354        left.set(0, false);
355        let mut right = Bitmap::all_valid(8);
356        right.set(1, false);
357        let both = Validity::Mask(left.clone()).and(&Validity::Mask(right), 8);
358        assert!(!both.is_valid(0));
359        assert!(!both.is_valid(1));
360        assert!(both.is_valid(2));
361        assert_eq!(both.count_valid(8), 6);
362
363        assert_eq!(Validity::AllValid.and(&Validity::AllValid, 8), Validity::AllValid);
364        assert_eq!(Validity::AllInvalid.and(&Validity::Mask(left), 8), Validity::AllInvalid);
365    }
366
367    #[test]
368    fn validity_from_a_predicate_normalizes_itself() {
369        assert_eq!(Validity::from_iter(16, |_| true), Validity::AllValid);
370        assert_eq!(Validity::from_iter(16, |_| false), Validity::AllInvalid);
371        let mixed = Validity::from_iter(16, |i| i % 2 == 0);
372        assert_eq!(mixed.count_valid(16), 8);
373    }
374}