Skip to content

Commit 46ad4bb

Browse files
committed
Add SIMD primitive comparison kernels
Moves the explicit AVX2 and AVX-512 primitive comparison kernels from #9547 into the handwritten comparison path, leaving the RowFn work in #9547 and #9548 untouched. Non-x86 targets and x86-64 CPUs without AVX2 keep the portable lane-kernel fallback. Full-word kernels cover every primitive type, comparison operator, and array/constant orientation, with scalar tail handling, split by lane width so each width reads on its own. Boundary and nullable coverage comes with them. Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
1 parent 9688ad0 commit 46ad4bb

13 files changed

Lines changed: 1888 additions & 8 deletions

File tree

vortex-array/src/scalar_fn/fns/binary/compare/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
//! Native comparison kernels.
55
//!
66
//! [`execute_compare`] dispatches on the logical [`DType`] of its operands and evaluates every
7-
//! comparison directly over Vortex canonical arraysbit buffers for booleans, lane kernels from
8-
//! `vortex-compute` for primitives and decimals, binary views for strings/bytes, and a row-wise
9-
//! comparator for nested types. There is no Arrow fallback.
7+
//! comparison directly over Vortex canonical arrays: bit buffers for booleans, explicit x86 SIMD
8+
//! or portable lane kernels for primitives, lane kernels for decimals, binary views for
9+
//! strings/bytes, and a row-wise comparator for nested types. There is no Arrow fallback.
1010
//!
1111
//! Floating point values compare with Vortex's total ordering (`NaN` is the largest value,
1212
//! `-0.0 < +0.0`, and equality is bitwise), matching [`Scalar`] comparison semantics.
@@ -282,8 +282,14 @@ pub(super) fn ordering_predicate(op: CompareOperator) -> fn(Ordering) -> bool {
282282
}
283283

284284
/// Freeze `len` bits packed into `words` (LSB-first, 64 lanes per word) into a [`BitBuffer`].
285-
pub(super) fn bit_buffer_from_words(words: BufferMut<u64>, len: usize) -> BitBuffer {
285+
pub(super) fn bit_buffer_from_words(mut words: BufferMut<u64>, len: usize) -> BitBuffer {
286286
debug_assert!(words.len() * 64 >= len);
287+
288+
// Byte reinterpretation must preserve LSB-first lane order on big-endian hosts.
289+
for word in words.iter_mut() {
290+
*word = word.to_le();
291+
}
292+
287293
let mut bytes = words.into_byte_buffer();
288294
bytes.truncate(len.div_ceil(8));
289295
BitBuffer::new(bytes.freeze(), len)

vortex-array/src/scalar_fn/fns/binary/compare/primitive.rs renamed to vortex-array/src/scalar_fn/fns/binary/compare/primitive/mod.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4-
//! Native comparison of primitive arrays via bit-packing lane kernels.
4+
//! Native comparison of primitive arrays with Vortex's scalar ordering semantics.
5+
//!
6+
//! The outer comparison path owns decoding, validity, and the portable bit-packing fallback.
7+
//! On x86-64, [`simd`] can replace the value loop with explicit AVX2 or AVX-512 kernels.
8+
9+
mod simd;
510

611
use vortex_buffer::BitBuffer;
712
use vortex_error::VortexResult;
@@ -41,7 +46,7 @@ pub(super) fn compare_primitive(
4146
})
4247
}
4348

44-
fn compare_primitive_typed<T: NativePType>(
49+
fn compare_primitive_typed<T: NativePType + simd::SimdCompare>(
4550
lhs: &ArrayRef,
4651
rhs: &ArrayRef,
4752
op: CompareOperator,
@@ -105,7 +110,15 @@ fn apply_op<T: NativePType>(lhs: T, rhs: T, op: CompareOperator) -> bool {
105110
}
106111
}
107112

108-
fn compare_slices<T: NativePType>(lhs: &[T], rhs: &[T], op: CompareOperator) -> BitBuffer {
113+
fn compare_slices<T: NativePType + simd::SimdCompare>(
114+
lhs: &[T],
115+
rhs: &[T],
116+
op: CompareOperator,
117+
) -> BitBuffer {
118+
if let Some(bits) = simd::try_compare_slices(lhs, rhs, op) {
119+
return bits;
120+
}
121+
109122
// Dispatch the operator outside the lane loop so each instantiation vectorizes a single
110123
// branch-free predicate.
111124
match op {
@@ -118,7 +131,15 @@ fn compare_slices<T: NativePType>(lhs: &[T], rhs: &[T], op: CompareOperator) ->
118131
}
119132
}
120133

121-
fn compare_slice_constant<T: NativePType>(lhs: &[T], rhs: T, op: CompareOperator) -> BitBuffer {
134+
fn compare_slice_constant<T: NativePType + simd::SimdCompare>(
135+
lhs: &[T],
136+
rhs: T,
137+
op: CompareOperator,
138+
) -> BitBuffer {
139+
if let Some(bits) = simd::try_compare_slice_constant(lhs, rhs, op) {
140+
return bits;
141+
}
142+
122143
match op {
123144
CompareOperator::Eq => collect_bits(lhs, |a: T| a.is_eq(rhs)),
124145
CompareOperator::NotEq => collect_bits(lhs, |a: T| !a.is_eq(rhs)),
@@ -128,3 +149,6 @@ fn compare_slice_constant<T: NativePType>(lhs: &[T], rhs: T, op: CompareOperator
128149
CompareOperator::Lte => collect_bits(lhs, |a: T| a.is_le(rhs)),
129150
}
130151
}
152+
153+
#[cfg(test)]
154+
mod tests;

0 commit comments

Comments
 (0)