diff --git a/arrow-ord/src/cmp.rs b/arrow-ord/src/cmp.rs index 2727ff996150..46cab1bb8e4c 100644 --- a/arrow-ord/src/cmp.rs +++ b/arrow-ord/src/cmp.rs @@ -565,13 +565,16 @@ impl<'a, T: ByteViewType> ArrayOrd for &'a GenericByteViewArray { /// Item.0 is the array, Item.1 is the index type Item = (&'a GenericByteViewArray, usize); + #[inline(always)] fn is_eq(l: Self::Item, r: Self::Item) -> bool { - // # Safety - // The index is within bounds as it is checked in value() let l_view = unsafe { l.0.views().get_unchecked(l.1) }; - let l_len = *l_view as u32; - let r_view = unsafe { r.0.views().get_unchecked(r.1) }; + if l.0.data_buffers().is_empty() && r.0.data_buffers().is_empty() { + // For eq case, we can directly compare the inlined bytes + return l_view.cmp(r_view).is_eq(); + } + + let l_len = *l_view as u32; let r_len = *r_view as u32; // This is a fast path for equality check. // We don't need to look at the actual bytes to determine if they are equal. @@ -579,10 +582,22 @@ impl<'a, T: ByteViewType> ArrayOrd for &'a GenericByteViewArray { return false; } + // # Safety + // The index is within bounds as it is checked in value() unsafe { GenericByteViewArray::compare_unchecked(l.0, l.1, r.0, r.1).is_eq() } } + #[inline(always)] fn is_lt(l: Self::Item, r: Self::Item) -> bool { + if l.0.data_buffers().is_empty() && r.0.data_buffers().is_empty() { + let l_view = unsafe { l.0.views().get_unchecked(l.1) }; + let r_view = unsafe { r.0.views().get_unchecked(r.1) }; + let l_len = *l_view as u32 as usize; + let r_len = *r_view as u32 as usize; + let l_bytes = unsafe { GenericByteViewArray::::inline_value(l_view, l_len) }; + let r_bytes = unsafe { GenericByteViewArray::::inline_value(r_view, r_len) }; + return l_bytes.cmp(r_bytes).is_lt(); + } // # Safety // The index is within bounds as it is checked in value() unsafe { GenericByteViewArray::compare_unchecked(l.0, l.1, r.0, r.1).is_lt() } @@ -618,6 +633,7 @@ impl<'a> ArrayOrd for &'a FixedSizeBinaryArray { } /// Compares two [`GenericByteViewArray`] at index `left_idx` and `right_idx` +#[inline(always)] pub fn compare_byte_view( left: &GenericByteViewArray, left_idx: usize, @@ -626,6 +642,15 @@ pub fn compare_byte_view( ) -> std::cmp::Ordering { assert!(left_idx < left.len()); assert!(right_idx < right.len()); + if left.data_buffers().is_empty() && right.data_buffers().is_empty() { + let l_view = unsafe { left.views().get_unchecked(left_idx) }; + let r_view = unsafe { right.views().get_unchecked(right_idx) }; + let l_len = *l_view as u32 as usize; + let r_len = *r_view as u32 as usize; + let l_bytes = unsafe { GenericByteViewArray::::inline_value(l_view, l_len) }; + let r_bytes = unsafe { GenericByteViewArray::::inline_value(r_view, r_len) }; + return l_bytes.cmp(r_bytes); + } unsafe { GenericByteViewArray::compare_unchecked(left, left_idx, right, right_idx) } } diff --git a/arrow/benches/comparison_kernels.rs b/arrow/benches/comparison_kernels.rs index c29200d8000a..6a02deb41ad5 100644 --- a/arrow/benches/comparison_kernels.rs +++ b/arrow/benches/comparison_kernels.rs @@ -69,6 +69,17 @@ fn make_string_array(size: usize, rng: &mut StdRng) -> impl Iterator impl Iterator> + '_ { + (0..size).map(|_| { + let len = rng.random_range(0..12); + let bytes = (0..len).map(|_| rng.random_range(0..128)).collect(); + Some(String::from_utf8(bytes).unwrap()) + }) +} + fn add_benchmark(c: &mut Criterion) { let arr_a = create_primitive_array_with_seed::(SIZE, 0.0, 42); let arr_b = create_primitive_array_with_seed::(SIZE, 0.0, 43); @@ -226,6 +237,23 @@ fn add_benchmark(c: &mut Criterion) { b.iter(|| eq(&string_view_left, &string_view_right).unwrap()) }); + let array_gen = make_inlined_string_array(1024 * 1024 * 8, &mut rng); + let string_left = StringArray::from_iter(array_gen); + let string_view_inlined_left = StringViewArray::from_iter(string_left.iter()); + + let array_gen = make_inlined_string_array(1024 * 1024 * 8, &mut rng); + let string_right = StringArray::from_iter(array_gen); + let string_view_inlined_right = StringViewArray::from_iter(string_right.iter()); + + // Add fast path benchmarks for StringViewArray, both side are inlined views < 12 bytes + c.bench_function("eq StringViewArray StringViewArray inlined bytes", |b| { + b.iter(|| eq(&string_view_inlined_left, &string_view_inlined_right).unwrap()) + }); + + c.bench_function("lt StringViewArray StringViewArray inlined bytes", |b| { + b.iter(|| lt(&string_view_inlined_left, &string_view_inlined_right).unwrap()) + }); + // eq benchmarks for long strings with the same prefix c.bench_function("eq long same prefix strings StringArray", |b| { b.iter(|| eq(&left_arr_long_string, &right_arr_long_string).unwrap())