|
24 | 24 | //!
|
25 | 25 |
|
26 | 26 | use arrow_array::cast::AsArray;
|
27 |
| -use arrow_array::types::ByteArrayType; |
| 27 | +use arrow_array::types::{ByteArrayType, ByteViewType}; |
28 | 28 | use arrow_array::{
|
29 | 29 | downcast_primitive_array, AnyDictionaryArray, Array, ArrowNativeTypeOp, BooleanArray, Datum,
|
30 |
| - FixedSizeBinaryArray, GenericByteArray, |
| 30 | + FixedSizeBinaryArray, GenericByteArray, GenericByteViewArray, |
31 | 31 | };
|
32 | 32 | use arrow_buffer::bit_util::ceil;
|
33 | 33 | use arrow_buffer::{BooleanBuffer, MutableBuffer, NullBuffer};
|
@@ -228,8 +228,10 @@ fn compare_op(op: Op, lhs: &dyn Datum, rhs: &dyn Datum) -> Result<BooleanArray,
|
228 | 228 | (l, r) => apply(op, l.values().as_ref(), l_s, l_v, r.values().as_ref(), r_s, r_v),
|
229 | 229 | (Boolean, Boolean) => apply(op, l.as_boolean(), l_s, l_v, r.as_boolean(), r_s, r_v),
|
230 | 230 | (Utf8, Utf8) => apply(op, l.as_string::<i32>(), l_s, l_v, r.as_string::<i32>(), r_s, r_v),
|
| 231 | + (Utf8View, Utf8View) => apply(op, l.as_string_view(), l_s, l_v, r.as_string_view(), r_s, r_v), |
231 | 232 | (LargeUtf8, LargeUtf8) => apply(op, l.as_string::<i64>(), l_s, l_v, r.as_string::<i64>(), r_s, r_v),
|
232 | 233 | (Binary, Binary) => apply(op, l.as_binary::<i32>(), l_s, l_v, r.as_binary::<i32>(), r_s, r_v),
|
| 234 | + (BinaryView, BinaryView) => apply(op, l.as_binary_view(), l_s, l_v, r.as_binary_view(), r_s, r_v), |
233 | 235 | (LargeBinary, LargeBinary) => apply(op, l.as_binary::<i64>(), l_s, l_v, r.as_binary::<i64>(), r_s, r_v),
|
234 | 236 | (FixedSizeBinary(_), FixedSizeBinary(_)) => apply(op, l.as_fixed_size_binary(), l_s, l_v, r.as_fixed_size_binary(), r_s, r_v),
|
235 | 237 | (Null, Null) => None,
|
@@ -459,7 +461,7 @@ fn apply_op_vectored<T: ArrayOrd>(
|
459 | 461 | }
|
460 | 462 |
|
461 | 463 | trait ArrayOrd {
|
462 |
| - type Item: Copy + Default; |
| 464 | + type Item: Copy; |
463 | 465 |
|
464 | 466 | fn len(&self) -> usize;
|
465 | 467 |
|
@@ -538,6 +540,109 @@ impl<'a, T: ByteArrayType> ArrayOrd for &'a GenericByteArray<T> {
|
538 | 540 | }
|
539 | 541 | }
|
540 | 542 |
|
| 543 | +/// Comparing two ByteView types are non-trivial. |
| 544 | +/// It takes a bit of patience to understand why we don't just compare two &[u8] directly. |
| 545 | +/// |
| 546 | +/// ByteView types give us the following two advantages, and we need to be careful not to lose them: |
| 547 | +/// (1) For string/byte smaller than 12 bytes, the entire data is inlined in the view. |
| 548 | +/// Meaning that reading one array element requires only one memory access |
| 549 | +/// (two memory access required for StringArray, one for offset buffer, the other for value buffer). |
| 550 | +/// |
| 551 | +/// (2) For string/byte larger than 12 bytes, we can still be faster than (for certain operations) StringArray/ByteArray, |
| 552 | +/// thanks to the inlined 4 bytes. |
| 553 | +/// Consider equality check: |
| 554 | +/// If the first four bytes of the two strings are different, we can return false immediately (with just one memory access). |
| 555 | +/// If we are unlucky and the first four bytes are the same, we need to fallback to compare two full strings. |
| 556 | +impl<'a, T: ByteViewType> ArrayOrd for &'a GenericByteViewArray<T> { |
| 557 | + /// Item.0 is the array, Item.1 is the index into the array. |
| 558 | + /// Why don't we just store Item.0[Item.1] as the item? |
| 559 | + /// - Because if we do so, we materialize the entire string (i.e., make multiple memory accesses), which might be unnecessary. |
| 560 | + /// - Most of the time (eq, ord), we only need to look at the first 4 bytes to know the answer, |
| 561 | + /// e.g., if the inlined 4 bytes are different, we can directly return unequal without looking at the full string. |
| 562 | + type Item = (&'a GenericByteViewArray<T>, usize); |
| 563 | + |
| 564 | + /// # Equality check flow |
| 565 | + /// (1) if both string are smaller than 12 bytes, we can directly compare the data inlined to the view. |
| 566 | + /// (2) if any of the string is larger than 12 bytes, we need to compare the full string. |
| 567 | + /// (2.1) if the inlined 4 bytes are different, we can return false immediately. |
| 568 | + /// (2.2) o.w., we need to compare the full string. |
| 569 | + /// |
| 570 | + /// # Safety |
| 571 | + /// (1) Indexing. The Self::Item.1 encodes the index value, which is already checked in `value` function, |
| 572 | + /// so it is safe to index into the views. |
| 573 | + /// (2) Slice data from view. We know the bytes 4-8 are inlined data (per spec), so it is safe to slice from the view. |
| 574 | + fn is_eq(l: Self::Item, r: Self::Item) -> bool { |
| 575 | + let l_view = unsafe { l.0.views().get_unchecked(l.1) }; |
| 576 | + let l_len = *l_view as u32; |
| 577 | + |
| 578 | + let r_view = unsafe { r.0.views().get_unchecked(r.1) }; |
| 579 | + let r_len = *r_view as u32; |
| 580 | + |
| 581 | + if l_len != r_len { |
| 582 | + return false; |
| 583 | + } |
| 584 | + |
| 585 | + if l_len <= 12 { |
| 586 | + let l_data = unsafe { GenericByteViewArray::<T>::inline_value(l_view, l_len as usize) }; |
| 587 | + let r_data = unsafe { GenericByteViewArray::<T>::inline_value(r_view, r_len as usize) }; |
| 588 | + l_data == r_data |
| 589 | + } else { |
| 590 | + let l_inlined_data = unsafe { GenericByteViewArray::<T>::inline_value(l_view, 4) }; |
| 591 | + let r_inlined_data = unsafe { GenericByteViewArray::<T>::inline_value(r_view, 4) }; |
| 592 | + if l_inlined_data != r_inlined_data { |
| 593 | + return false; |
| 594 | + } |
| 595 | + |
| 596 | + let l_full_data: &[u8] = unsafe { l.0.value_unchecked(l.1).as_ref() }; |
| 597 | + let r_full_data: &[u8] = unsafe { r.0.value_unchecked(r.1).as_ref() }; |
| 598 | + l_full_data == r_full_data |
| 599 | + } |
| 600 | + } |
| 601 | + |
| 602 | + /// # Ordering check flow |
| 603 | + /// (1) if both string are smaller than 12 bytes, we can directly compare the data inlined to the view. |
| 604 | + /// (2) if any of the string is larger than 12 bytes, we need to compare the full string. |
| 605 | + /// (2.1) if the inlined 4 bytes are different, we can return the result immediately. |
| 606 | + /// (2.2) o.w., we need to compare the full string. |
| 607 | + /// |
| 608 | + /// # Safety |
| 609 | + /// (1) Indexing. The Self::Item.1 encodes the index value, which is already checked in `value` function, |
| 610 | + /// so it is safe to index into the views. |
| 611 | + /// (2) Slice data from view. We know the bytes 4-8 are inlined data (per spec), so it is safe to slice from the view. |
| 612 | + fn is_lt(l: Self::Item, r: Self::Item) -> bool { |
| 613 | + let l_view = l.0.views().get(l.1).unwrap(); |
| 614 | + let l_len = *l_view as u32; |
| 615 | + |
| 616 | + let r_view = r.0.views().get(r.1).unwrap(); |
| 617 | + let r_len = *r_view as u32; |
| 618 | + |
| 619 | + if l_len <= 12 && r_len <= 12 { |
| 620 | + let l_data = unsafe { GenericByteViewArray::<T>::inline_value(l_view, l_len as usize) }; |
| 621 | + let r_data = unsafe { GenericByteViewArray::<T>::inline_value(r_view, r_len as usize) }; |
| 622 | + return l_data < r_data; |
| 623 | + } |
| 624 | + // one of the string is larger than 12 bytes, |
| 625 | + // we then try to compare the inlined data first |
| 626 | + let l_inlined_data = unsafe { GenericByteViewArray::<T>::inline_value(l_view, 4) }; |
| 627 | + let r_inlined_data = unsafe { GenericByteViewArray::<T>::inline_value(r_view, 4) }; |
| 628 | + if r_inlined_data != l_inlined_data { |
| 629 | + return l_inlined_data < r_inlined_data; |
| 630 | + } |
| 631 | + // unfortunately, we need to compare the full data |
| 632 | + let l_full_data: &[u8] = unsafe { l.0.value_unchecked(l.1).as_ref() }; |
| 633 | + let r_full_data: &[u8] = unsafe { r.0.value_unchecked(r.1).as_ref() }; |
| 634 | + l_full_data < r_full_data |
| 635 | + } |
| 636 | + |
| 637 | + fn len(&self) -> usize { |
| 638 | + Array::len(self) |
| 639 | + } |
| 640 | + |
| 641 | + unsafe fn value_unchecked(&self, idx: usize) -> Self::Item { |
| 642 | + (self, idx) |
| 643 | + } |
| 644 | +} |
| 645 | + |
541 | 646 | impl<'a> ArrayOrd for &'a FixedSizeBinaryArray {
|
542 | 647 | type Item = &'a [u8];
|
543 | 648 |
|
|
0 commit comments