|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// SPDX-FileCopyrightText: Copyright the Vortex contributors |
| 3 | + |
| 4 | +use fastlanes::FastLanes; |
| 5 | +use vortex_array::arrays::PrimitiveArray; |
| 6 | +use vortex_array::stats::ArrayStats; |
| 7 | +use vortex_array::validity::Validity; |
| 8 | +use vortex_array::{ArrayRef, IntoArray}; |
| 9 | +use vortex_buffer::Buffer; |
| 10 | +use vortex_dtype::{DType, NativePType, PType, match_each_unsigned_integer_ptype}; |
| 11 | +use vortex_error::{VortexExpect as _, VortexResult, vortex_bail}; |
| 12 | + |
| 13 | +pub mod delta_compress; |
| 14 | +pub mod delta_decompress; |
| 15 | + |
| 16 | +/// A FastLanes-style delta-encoded array of primitive values. |
| 17 | +/// |
| 18 | +/// A [`DeltaArray`] comprises a sequence of _chunks_ each representing 1,024 delta-encoded values, |
| 19 | +/// except the last chunk which may represent from one to 1,024 values. |
| 20 | +/// |
| 21 | +/// # Examples |
| 22 | +/// |
| 23 | +/// ``` |
| 24 | +/// use vortex_fastlanes::DeltaArray; |
| 25 | +/// let array = DeltaArray::try_from_vec(vec![1_u32, 2, 3, 5, 10, 11]).unwrap(); |
| 26 | +/// ``` |
| 27 | +/// |
| 28 | +/// # Details |
| 29 | +/// |
| 30 | +/// To facilitate slicing, this array accepts an `offset` and `logical_len`. The offset must be |
| 31 | +/// strictly less than 1,024 and the sum of `offset` and `logical_len` must not exceed the length of |
| 32 | +/// the `deltas` array. These values permit logical slicing without modifying any chunk containing a |
| 33 | +/// kept value. In particular, we may defer decompresison until the array is canonicalized or |
| 34 | +/// indexed. The `offset` is a physical offset into the first chunk, which necessarily contains |
| 35 | +/// 1,024 values. The `logical_len` is the number of logical values following the `offset`, which |
| 36 | +/// may be less than the number of physically stored values. |
| 37 | +/// |
| 38 | +/// Each chunk is stored as a vector of bases and a vector of deltas. If the chunk physically |
| 39 | +/// contains 1,024 values, then there are as many bases as there are _lanes_ of this type in a |
| 40 | +/// 1024-bit register. For example, for 64-bit values, there are 16 bases because there are 16 |
| 41 | +/// _lanes_. Each lane is a [delta-encoding](https://en.wikipedia.org/wiki/Delta_encoding) `1024 / |
| 42 | +/// bit_width` long vector of values. The deltas are stored in the |
| 43 | +/// [FastLanes](https://www.vldb.org/pvldb/vol16/p2132-afroozeh.pdf) order which splits the 1,024 |
| 44 | +/// values into one contiguous sub-sequence per-lane, thus permitting delta encoding. |
| 45 | +/// |
| 46 | +/// If the chunk physically has fewer than 1,024 values, then it is stored as a traditional, |
| 47 | +/// non-SIMD-amenable, delta-encoded vector. |
| 48 | +/// |
| 49 | +/// Note the validity is stored in the deltas array. |
| 50 | +#[derive(Clone, Debug)] |
| 51 | +pub struct DeltaArray { |
| 52 | + offset: usize, |
| 53 | + len: usize, |
| 54 | + dtype: DType, |
| 55 | + bases: ArrayRef, |
| 56 | + deltas: ArrayRef, |
| 57 | + stats_set: ArrayStats, |
| 58 | +} |
| 59 | + |
| 60 | +impl DeltaArray { |
| 61 | + // TODO(ngates): remove constructing from vec |
| 62 | + pub fn try_from_vec<T: NativePType>(vec: Vec<T>) -> VortexResult<Self> { |
| 63 | + Self::try_from_primitive_array(&PrimitiveArray::new( |
| 64 | + Buffer::copy_from(vec), |
| 65 | + Validity::NonNullable, |
| 66 | + )) |
| 67 | + } |
| 68 | + |
| 69 | + pub fn try_from_primitive_array(array: &PrimitiveArray) -> VortexResult<Self> { |
| 70 | + let (bases, deltas) = delta_compress::delta_compress(array)?; |
| 71 | + |
| 72 | + Self::try_from_delta_compress_parts(bases.into_array(), deltas.into_array()) |
| 73 | + } |
| 74 | + |
| 75 | + /// Create a [`DeltaArray`] from the given `bases` and `deltas` arrays. |
| 76 | + /// Note the `deltas` might be nullable |
| 77 | + pub fn try_from_delta_compress_parts(bases: ArrayRef, deltas: ArrayRef) -> VortexResult<Self> { |
| 78 | + let logical_len = deltas.len(); |
| 79 | + Self::try_new(bases, deltas, 0, logical_len) |
| 80 | + } |
| 81 | + |
| 82 | + pub fn try_new( |
| 83 | + bases: ArrayRef, |
| 84 | + deltas: ArrayRef, |
| 85 | + offset: usize, |
| 86 | + logical_len: usize, |
| 87 | + ) -> VortexResult<Self> { |
| 88 | + if offset >= 1024 { |
| 89 | + vortex_bail!("offset must be less than 1024: {}", offset); |
| 90 | + } |
| 91 | + if offset + logical_len > deltas.len() { |
| 92 | + vortex_bail!( |
| 93 | + "offset + logical_len, {} + {}, must be less than or equal to the size of deltas: {}", |
| 94 | + offset, |
| 95 | + logical_len, |
| 96 | + deltas.len() |
| 97 | + ) |
| 98 | + } |
| 99 | + if !bases.dtype().eq_ignore_nullability(deltas.dtype()) { |
| 100 | + vortex_bail!( |
| 101 | + "DeltaArray: bases and deltas must have the same dtype, got {:?} and {:?}", |
| 102 | + bases.dtype(), |
| 103 | + deltas.dtype() |
| 104 | + ); |
| 105 | + } |
| 106 | + let DType::Primitive(ptype, _) = bases.dtype().clone() else { |
| 107 | + vortex_bail!( |
| 108 | + "DeltaArray: dtype must be an integer, got {}", |
| 109 | + bases.dtype() |
| 110 | + ); |
| 111 | + }; |
| 112 | + |
| 113 | + if !ptype.is_int() { |
| 114 | + vortex_bail!("DeltaArray: ptype must be an integer, got {}", ptype); |
| 115 | + } |
| 116 | + |
| 117 | + let lanes = lane_count(ptype); |
| 118 | + |
| 119 | + if (deltas.len() % 1024 == 0) != (bases.len() % lanes == 0) { |
| 120 | + vortex_bail!( |
| 121 | + "deltas length ({}) is a multiple of 1024 iff bases length ({}) is a multiple of LANES ({})", |
| 122 | + deltas.len(), |
| 123 | + bases.len(), |
| 124 | + lanes, |
| 125 | + ); |
| 126 | + } |
| 127 | + |
| 128 | + // SAFETY: validation done above |
| 129 | + Ok(unsafe { Self::new_unchecked(bases, deltas, offset, logical_len) }) |
| 130 | + } |
| 131 | + |
| 132 | + pub(crate) unsafe fn new_unchecked( |
| 133 | + bases: ArrayRef, |
| 134 | + deltas: ArrayRef, |
| 135 | + offset: usize, |
| 136 | + logical_len: usize, |
| 137 | + ) -> Self { |
| 138 | + Self { |
| 139 | + offset, |
| 140 | + len: logical_len, |
| 141 | + dtype: bases.dtype().with_nullability(deltas.dtype().nullability()), |
| 142 | + bases, |
| 143 | + deltas, |
| 144 | + stats_set: Default::default(), |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + #[inline] |
| 149 | + pub fn bases(&self) -> &ArrayRef { |
| 150 | + &self.bases |
| 151 | + } |
| 152 | + |
| 153 | + #[inline] |
| 154 | + pub fn deltas(&self) -> &ArrayRef { |
| 155 | + &self.deltas |
| 156 | + } |
| 157 | + |
| 158 | + #[inline] |
| 159 | + pub(crate) fn lanes(&self) -> usize { |
| 160 | + let ptype = |
| 161 | + PType::try_from(self.dtype()).vortex_expect("DeltaArray DType must be primitive"); |
| 162 | + lane_count(ptype) |
| 163 | + } |
| 164 | + |
| 165 | + #[inline] |
| 166 | + pub fn len(&self) -> usize { |
| 167 | + self.len |
| 168 | + } |
| 169 | + |
| 170 | + #[inline] |
| 171 | + pub fn is_empty(&self) -> bool { |
| 172 | + self.len == 0 |
| 173 | + } |
| 174 | + |
| 175 | + #[inline] |
| 176 | + pub fn dtype(&self) -> &DType { |
| 177 | + &self.dtype |
| 178 | + } |
| 179 | + |
| 180 | + #[inline] |
| 181 | + /// The logical offset into the first chunk of [`Self::deltas`]. |
| 182 | + pub fn offset(&self) -> usize { |
| 183 | + self.offset |
| 184 | + } |
| 185 | + |
| 186 | + #[inline] |
| 187 | + pub(crate) fn bases_len(&self) -> usize { |
| 188 | + self.bases.len() |
| 189 | + } |
| 190 | + |
| 191 | + #[inline] |
| 192 | + pub(crate) fn deltas_len(&self) -> usize { |
| 193 | + self.deltas.len() |
| 194 | + } |
| 195 | + |
| 196 | + #[inline] |
| 197 | + pub(crate) fn stats_set(&self) -> &ArrayStats { |
| 198 | + &self.stats_set |
| 199 | + } |
| 200 | +} |
| 201 | + |
| 202 | +pub(crate) fn lane_count(ptype: PType) -> usize { |
| 203 | + match_each_unsigned_integer_ptype!(ptype, |T| { T::LANES }) |
| 204 | +} |
0 commit comments