rudb_kernels/lib.rs
1//! The compute kernels: casting, comparison, arithmetic, three-valued logic, the aggregates and
2//! turning a vector of flags into the rows it keeps.
3//!
4//! Rank 3 in the layer rule. See `xtask/layers.toml` and `spec/18-package-layout.md`.
5//!
6//! Everything here takes vectors and values and knows nothing about plans, operators or catalogs.
7//! That is what makes it callable from the interpreter, from the fused kernels of tier 1 and from
8//! a test that wants to check one conversion, and it is why the comparison enum in [`Comparison`] is
9//! this crate's own rather than the plan's.
10//!
11//! # What tier this is
12//!
13//! `spec/08-codegen.md` section 8.1 puts four tiers on the table and says tier 0 is never optional,
14//! because it is the reference every faster tier is differentially tested against. This crate is
15//! the compute half of tier 0.
16//!
17//! Every kernel here takes a vector and produces a vector. Each of them started as a scalar loop
18//! over [`rudb_vector::Vector::value_at`], which was slow on purpose and slow in a way that was
19//! visible, because the interface was already the batch interface and a specialization that reads a
20//! `&[i32]` out of a flat vector replaces a body without touching a caller. Sub-milestone 2b is
21//! where that replacement happens, one file at a time, each with a microbenchmark next to it.
22//!
23//! All five files are done. [`compare::compare`], [`scalar::call`], [`logic::combine`] and
24//! [`cast::cast`] dispatch once on the form pair and once on the physical layout, hoist everything
25//! that does not change from row to row out of the loop, and keep the old row at a time loop as the
26//! oracle their property tests check against rather than as dead code. The fifth,
27//! [`aggregate::Accumulator`], is the odd one out because it has state rather than an output vector,
28//! so its batch interface folds a vector into the running state instead of returning one. The
29//! shapes none of them has a loop for still run the old loop, and they are still correct.
30//!
31//! [`select::selection`] arrived after those five and is the other end of the same measurement.
32//! A comparison that produces a boolean vector in under a nanosecond a row is no use if the
33//! operator above it then reads that vector back a value at a time, which is what a filter was
34//! doing, so the sixth file turns the flags into the positions that survived without a branch in
35//! the loop.
36//!
37//! The other optimization that has been here from the start is the constant fast path: a cast or a
38//! comparison where both sides are constant vectors costs one operation rather than 1024. That one
39//! is worth having because the binder turns every literal in a predicate into a constant vector, so
40//! it is on the path of the first query anybody runs.
41//!
42//! # Knowing what to specialize next
43//!
44//! There are four physical forms and so sixteen form pairs, and a hand written loop for all sixteen
45//! of them in every kernel is both a lot of code and a lot of places for a wrong answer to hide.
46//! The rule this crate follows instead is to specialize the pairs a scan actually produces and to
47//! count the rest. [`fallback`] is the counter. A kernel that falls through to the row at a time
48//! path increments a cell, a harness prints the cells that are not zero at the end of a run, and a
49//! pair worth another loop then arrives as a number rather than as an opinion.
50//!
51//! # What is not here
52//!
53//! Encoded vector specialization, which arrives with the fifth form at layer three. SIMD, which the
54//! generator of section 7.3 produces rather than a person writing it. Regular expressions, date
55//! arithmetic, the string functions past the four here, and the statistical aggregates. Each of
56//! those is a signature in `rudb-functions` before it is a kernel here, so the missing ones fail at
57//! binding with a message naming the function rather than here with a message naming a match arm.
58
59#![forbid(unsafe_code)]
60
61pub mod aggregate;
62pub mod cast;
63pub mod compare;
64pub mod fallback;
65pub mod logic;
66mod number;
67pub mod scalar;
68pub mod select;
69mod shape;
70
71pub use aggregate::Accumulator;
72pub use cast::{cast, cast_value};
73pub use compare::{Comparison, compare, compare_values, order, order_with_nulls};
74pub use fallback::Kernel;
75pub use logic::{Connective, combine, is_true};
76pub use scalar::{call, call_values};
77pub use select::selection;