This is an automated email from the ASF dual-hosted git repository.

etseidl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new ef94f4fa20 add const generic to take kernel (#10820)
ef94f4fa20 is described below

commit ef94f4fa200dc7f821743240dbd912892929ecb9
Author: RIchard Baah <[email protected]>
AuthorDate: Thu Aug 27 16:13:30 2026 -0400

    add const generic to take kernel (#10820)
    
    # Which issue does this PR close?
    
    - first step in closing  #8879.
    - this PR is blocking #10813
    
    # Rationale for this change
    Several hot paths in take perform redundant bounds checks on indices
    that have already been validated upfront via `check_bounds`. Threading a
    compile-time const CHECKED: bool through the call chain gives later PRs
    a zero-cost hook to switch to unchecked accessors (e.g.
    `value_unchecked`) when bounds are guaranteed, without changing the
    public API or default behavior.
    
    Why is this zero-cost (compute wise, not in terms of binary size)? :
    https://rustc-dev-guide.rust-lang.org/backend/monomorph.html
    
    # What changes are included in this PR?
    
    - Adds const CHECKED: bool to `take_impl`, `take_primitive`,
    `take_nulls`, `take_bits`, `take_boolean`, `take_bytes`,
    `take_byte_view`, `take_list`, `take_list_view`, `take_fixed_size_list`,
    `take_fixed_size_binary`, and `take_dict`
    - All callers thread the parameter through; the public take function
    always passes true for now
    - benchmarks show no regressions across all take kernel variants
    
    # Are these changes tested?
    nothing new to test.
    n/a
    
    # Are there any user-facing changes?
    no
---
 arrow-select/src/take.rs | 98 ++++++++++++++++++++++++------------------------
 1 file changed, 50 insertions(+), 48 deletions(-)

diff --git a/arrow-select/src/take.rs b/arrow-select/src/take.rs
index b66dbf5463..9ddbf38083 100644
--- a/arrow-select/src/take.rs
+++ b/arrow-select/src/take.rs
@@ -98,7 +98,7 @@ pub fn take(
                 check_bounds(values.len(), indices)?;
             }
             let indices = indices.to_indices();
-            take_impl(values, &indices)
+            take_impl::<_, true>(values, &indices)
         },
         d => Err(ArrowError::InvalidArgumentError(format!("Take only supported 
for integers, got {d:?}")))
     )
@@ -209,7 +209,7 @@ where
 }
 
 #[inline(never)]
-fn take_impl<IndexType: ArrowPrimitiveType>(
+fn take_impl<IndexType: ArrowPrimitiveType, const CHECKED: bool>(
     values: &dyn Array,
     indices: &PrimitiveArray<IndexType>,
 ) -> Result<ArrayRef, ArrowError> {
@@ -217,38 +217,38 @@ fn take_impl<IndexType: ArrowPrimitiveType>(
         return Ok(new_empty_array(values.data_type()));
     }
     downcast_primitive_array! {
-        values => Ok(Arc::new(take_primitive(values, indices)?)),
+        values => Ok(Arc::new(take_primitive::<_, _, CHECKED>(values, 
indices)?)),
         DataType::Boolean => {
             let values = 
values.as_any().downcast_ref::<BooleanArray>().unwrap();
-            Ok(Arc::new(take_boolean(values, indices)))
+            Ok(Arc::new(take_boolean::<_, CHECKED>(values, indices)))
         }
         DataType::Utf8 => {
-            Ok(Arc::new(take_bytes(values.as_string::<i32>(), indices)?))
+            Ok(Arc::new(take_bytes::<_, _, CHECKED>(values.as_string::<i32>(), 
indices)?))
         }
         DataType::LargeUtf8 => {
-            Ok(Arc::new(take_bytes(values.as_string::<i64>(), indices)?))
+            Ok(Arc::new(take_bytes::<_, _, CHECKED>(values.as_string::<i64>(), 
indices)?))
         }
         DataType::Utf8View => {
-            Ok(Arc::new(take_byte_view(values.as_string_view(), indices)?))
+            Ok(Arc::new(take_byte_view::<_, _, 
CHECKED>(values.as_string_view(), indices)?))
         }
         DataType::List(_) => {
-            Ok(Arc::new(take_list::<_, Int32Type>(values.as_list(), indices)?))
+            Ok(Arc::new(take_list::<_, Int32Type, CHECKED>(values.as_list(), 
indices)?))
         }
         DataType::LargeList(_) => {
-            Ok(Arc::new(take_list::<_, Int64Type>(values.as_list(), indices)?))
+            Ok(Arc::new(take_list::<_, Int64Type, CHECKED>(values.as_list(), 
indices)?))
         }
         DataType::ListView(_) => {
-            Ok(Arc::new(take_list_view::<_, Int32Type>(values.as_list_view(), 
indices)?))
+            Ok(Arc::new(take_list_view::<_, Int32Type, 
CHECKED>(values.as_list_view(), indices)?))
         }
         DataType::LargeListView(_) => {
-            Ok(Arc::new(take_list_view::<_, Int64Type>(values.as_list_view(), 
indices)?))
+            Ok(Arc::new(take_list_view::<_, Int64Type, 
CHECKED>(values.as_list_view(), indices)?))
         }
         DataType::FixedSizeList(_, length) => {
             let values = values
                 .as_any()
                 .downcast_ref::<FixedSizeListArray>()
                 .unwrap();
-            Ok(Arc::new(take_fixed_size_list(
+            Ok(Arc::new(take_fixed_size_list::<_, CHECKED>(
                 values,
                 indices,
                 *length as u32,
@@ -256,7 +256,7 @@ fn take_impl<IndexType: ArrowPrimitiveType>(
         }
         DataType::Map(field, ordered) => {
             let list_arr = ListArray::from(values.as_map().clone());
-            let list_data = take_list::<_, Int32Type>(&list_arr, indices)?;
+            let list_data = take_list::<_, Int32Type, CHECKED>(&list_arr, 
indices)?;
             let (_, offsets, entries, nulls) = list_data.into_parts();
             let entries = entries.as_struct().clone();
             Ok(Arc::new(MapArray::try_new(
@@ -272,7 +272,7 @@ fn take_impl<IndexType: ArrowPrimitiveType>(
             let arrays  = array
                 .columns()
                 .iter()
-                .map(|a| take_impl(a.as_ref(), indices))
+                .map(|a| take_impl::<_, CHECKED>(a.as_ref(), indices))
                 .collect::<Result<Vec<ArrayRef>, _>>()?;
             let fields: Vec<(FieldRef, ArrayRef)> =
                 fields.iter().cloned().zip(arrays).collect();
@@ -297,7 +297,7 @@ fn take_impl<IndexType: ArrowPrimitiveType>(
             }
         }
         DataType::Dictionary(_, _) => downcast_dictionary_array! {
-            values => Ok(Arc::new(take_dict(values, indices)?)),
+            values => Ok(Arc::new(take_dict::<_, _, CHECKED>(values, 
indices)?)),
             t => unimplemented!("Take not supported for dictionary type {:?}", 
t)
         }
         DataType::RunEndEncoded(_, _) => downcast_run_array! {
@@ -305,20 +305,20 @@ fn take_impl<IndexType: ArrowPrimitiveType>(
             t => unimplemented!("Take not supported for run type {:?}", t)
         }
         DataType::Binary => {
-            Ok(Arc::new(take_bytes(values.as_binary::<i32>(), indices)?))
+            Ok(Arc::new(take_bytes::<_, _, CHECKED>(values.as_binary::<i32>(), 
indices)?))
         }
         DataType::LargeBinary => {
-            Ok(Arc::new(take_bytes(values.as_binary::<i64>(), indices)?))
+            Ok(Arc::new(take_bytes::<_, _, CHECKED>(values.as_binary::<i64>(), 
indices)?))
         }
         DataType::BinaryView => {
-            Ok(Arc::new(take_byte_view(values.as_binary_view(), indices)?))
+            Ok(Arc::new(take_byte_view::<_, _, 
CHECKED>(values.as_binary_view(), indices)?))
         }
         DataType::FixedSizeBinary(size) => {
             let values = values
                 .as_any()
                 .downcast_ref::<FixedSizeBinaryArray>()
                 .unwrap();
-            Ok(Arc::new(take_fixed_size_binary(values, indices, *size)?))
+            Ok(Arc::new(take_fixed_size_binary::<_, CHECKED>(values, indices, 
*size)?))
         }
         DataType::Null => {
             // Take applied to a null array produces a null array.
@@ -337,7 +337,7 @@ fn take_impl<IndexType: ArrowPrimitiveType>(
             let type_ids = take_native(values.type_ids(), indices);
             for (type_id, _field) in fields.iter() {
                 let values = values.child(type_id);
-                let values = take_impl(values, indices)?;
+                let values = take_impl::<_, CHECKED>(values, indices)?;
                 children.push(values);
             }
             let array = UnionArray::try_new(fields.clone(), type_ids, None, 
children)?;
@@ -357,7 +357,7 @@ fn take_impl<IndexType: ArrowPrimitiveType>(
 
                     let values = values.child(field_type_id);
 
-                    take_impl(values, indices.as_primitive::<Int32Type>())
+                    take_impl::<_, CHECKED>(values, 
indices.as_primitive::<Int32Type>())
                 })
                 .collect::<Result<_, _>>()?;
 
@@ -402,7 +402,7 @@ pub struct TakeOptions {
 ///     values:  [1, 2, 3, null, 5]
 ///     indices: [0, null, 4, 3]
 /// The result is: [1 (slot 0), null (null slot), 5 (slot 4), null (slot 3)]
-fn take_primitive<T, I>(
+fn take_primitive<T, I, const CHECKED: bool>(
     values: &PrimitiveArray<T>,
     indices: &PrimitiveArray<I>,
 ) -> Result<PrimitiveArray<T>, ArrowError>
@@ -411,18 +411,18 @@ where
     I: ArrowPrimitiveType,
 {
     let values_buf = take_native(values.values(), indices);
-    let nulls = take_nulls(values.nulls(), indices);
+    let nulls = take_nulls::<_, CHECKED>(values.nulls(), indices);
     Ok(PrimitiveArray::try_new(values_buf, 
nulls)?.with_data_type(values.data_type().clone()))
 }
 
 #[inline(never)]
-fn take_nulls<I: ArrowPrimitiveType>(
+fn take_nulls<I: ArrowPrimitiveType, const CHECKED: bool>(
     values: Option<&NullBuffer>,
     indices: &PrimitiveArray<I>,
 ) -> Option<NullBuffer> {
     match values.filter(|n| n.null_count() > 0) {
         Some(n) => NullBuffer::from_unsliced_buffer(
-            take_bits(n.inner(), indices).into_inner(),
+            take_bits::<_, CHECKED>(n.inner(), indices).into_inner(),
             indices.len(),
         ),
         None => indices.nulls().cloned(),
@@ -457,7 +457,7 @@ fn take_native<T: ArrowNativeType, I: ArrowPrimitiveType>(
 }
 
 #[inline(never)]
-fn take_bits<I: ArrowPrimitiveType>(
+fn take_bits<I: ArrowPrimitiveType, const CHECKED: bool>(
     values: &BooleanBuffer,
     indices: &PrimitiveArray<I>,
 ) -> BooleanBuffer {
@@ -469,7 +469,7 @@ fn take_bits<I: ArrowPrimitiveType>(
             let output_slice = output_buffer.as_slice_mut();
             nulls.valid_indices().for_each(|idx| {
                 // SAFETY: idx is a valid index in indices.nulls() --> 
idx<indices.len()
-                if values.value(unsafe { 
indices.value_unchecked(idx).as_usize() }) {
+                if unsafe { 
values.value(indices.value_unchecked(idx).as_usize()) } {
                     // SAFETY: MutableBuffer was created with space for 
indices.len() bit, and idx < indices.len()
                     unsafe { bit_util::set_bit_raw(output_slice.as_mut_ptr(), 
idx) };
                 }
@@ -486,17 +486,17 @@ fn take_bits<I: ArrowPrimitiveType>(
 }
 
 /// `take` implementation for boolean arrays
-fn take_boolean<IndexType: ArrowPrimitiveType>(
+fn take_boolean<IndexType: ArrowPrimitiveType, const CHECKED: bool>(
     values: &BooleanArray,
     indices: &PrimitiveArray<IndexType>,
 ) -> BooleanArray {
-    let val_buf = take_bits(values.values(), indices);
-    let null_buf = take_nulls(values.nulls(), indices);
+    let val_buf = take_bits::<_, CHECKED>(values.values(), indices);
+    let null_buf = take_nulls::<_, CHECKED>(values.nulls(), indices);
     BooleanArray::new(val_buf, null_buf)
 }
 
 /// `take` implementation for string arrays
-fn take_bytes<T: ByteArrayType, IndexType: ArrowPrimitiveType>(
+fn take_bytes<T: ByteArrayType, IndexType: ArrowPrimitiveType, const CHECKED: 
bool>(
     array: &GenericByteArray<T>,
     indices: &PrimitiveArray<IndexType>,
 ) -> Result<GenericByteArray<T>, ArrowError> {
@@ -506,7 +506,7 @@ fn take_bytes<T: ByteArrayType, IndexType: 
ArrowPrimitiveType>(
 
     let input_offsets = array.value_offsets();
     let mut capacity = 0;
-    let nulls = take_nulls(array.nulls(), indices);
+    let nulls = take_nulls::<_, CHECKED>(array.nulls(), indices);
 
     // Branch on output nulls — `None` means every output slot is valid.
     match nulls.as_ref().filter(|n| n.null_count() > 0) {
@@ -627,12 +627,12 @@ fn take_bytes<T: ByteArrayType, IndexType: 
ArrowPrimitiveType>(
 }
 
 /// `take` implementation for byte view arrays
-fn take_byte_view<T: ByteViewType, IndexType: ArrowPrimitiveType>(
+fn take_byte_view<T: ByteViewType, IndexType: ArrowPrimitiveType, const 
CHECKED: bool>(
     array: &GenericByteViewArray<T>,
     indices: &PrimitiveArray<IndexType>,
 ) -> Result<GenericByteViewArray<T>, ArrowError> {
     let new_views = take_native(array.views(), indices);
-    let new_nulls = take_nulls(array.nulls(), indices);
+    let new_nulls = take_nulls::<_, CHECKED>(array.nulls(), indices);
     let buffers = Arc::clone(array.data_buffers());
     // Safety:  array.views was valid, and take_native copies only valid 
values, and verifies bounds
     Ok(unsafe { GenericByteViewArray::new_unchecked(new_views, buffers, 
new_nulls) })
@@ -642,7 +642,7 @@ fn take_byte_view<T: ByteViewType, IndexType: 
ArrowPrimitiveType>(
 ///
 /// Copies the selected list entries' child slices into a new child array
 /// via `MutableArrayData`, then reconstructs a list array with new offsets
-fn take_list<IndexType, OffsetType>(
+fn take_list<IndexType, OffsetType, const CHECKED: bool>(
     values: &GenericListArray<OffsetType::Native>,
     indices: &PrimitiveArray<IndexType>,
 ) -> Result<GenericListArray<OffsetType::Native>, ArrowError>
@@ -654,7 +654,7 @@ where
 {
     let list_offsets = values.value_offsets();
     let child_data = values.values().to_data();
-    let nulls = take_nulls(values.nulls(), indices);
+    let nulls = take_nulls::<_, CHECKED>(values.nulls(), indices);
 
     let mut new_offsets = Vec::with_capacity(indices.len() + 1);
     new_offsets.push(OffsetType::Native::zero());
@@ -725,7 +725,7 @@ where
     GenericListArray::<OffsetType::Native>::try_new(field, offsets, child, 
nulls)
 }
 
-fn take_list_view<IndexType, OffsetType>(
+fn take_list_view<IndexType, OffsetType, const CHECKED: bool>(
     values: &GenericListViewArray<OffsetType::Native>,
     indices: &PrimitiveArray<IndexType>,
 ) -> Result<GenericListViewArray<OffsetType::Native>, ArrowError>
@@ -736,7 +736,7 @@ where
 {
     let taken_offsets = take_native(values.offsets(), indices);
     let taken_sizes = take_native(values.sizes(), indices);
-    let nulls = take_nulls(values.nulls(), indices);
+    let nulls = take_nulls::<_, CHECKED>(values.nulls(), indices);
 
     let field = match values.data_type() {
         DataType::ListView(field) | DataType::LargeListView(field) => 
field.clone(),
@@ -761,13 +761,13 @@ where
 /// Calculates the index and indexed offset for the inner array,
 /// applying `take` on the inner array, then reconstructing a list array
 /// with the indexed offsets
-fn take_fixed_size_list<IndexType: ArrowPrimitiveType>(
+fn take_fixed_size_list<IndexType: ArrowPrimitiveType, const CHECKED: bool>(
     values: &FixedSizeListArray,
     indices: &PrimitiveArray<IndexType>,
     length: <UInt32Type as ArrowPrimitiveType>::Native,
 ) -> Result<FixedSizeListArray, ArrowError> {
     let list_indices = take_value_indices_from_fixed_size_list(values, 
indices, length)?;
-    let taken = take_impl::<UInt32Type>(values.values().as_ref(), 
&list_indices)?;
+    let taken = take_impl::<UInt32Type, CHECKED>(values.values().as_ref(), 
&list_indices)?;
 
     // determine null count and null buffer, which are a function of `values` 
and `indices`
     let num_bytes = bit_util::ceil(indices.len(), 8);
@@ -798,7 +798,7 @@ fn take_fixed_size_list<IndexType: ArrowPrimitiveType>(
 /// The computation is done in two steps:
 /// - Compute the values buffer
 /// - Compute the null buffer
-fn take_fixed_size_binary<IndexType: ArrowPrimitiveType>(
+fn take_fixed_size_binary<IndexType: ArrowPrimitiveType, const CHECKED: bool>(
     values: &FixedSizeBinaryArray,
     indices: &PrimitiveArray<IndexType>,
     size: i32,
@@ -816,7 +816,7 @@ fn take_fixed_size_binary<IndexType: ArrowPrimitiveType>(
         _ => take_fixed_size_binary_buffer_dynamic_length(values, indices, 
size_usize),
     };
 
-    let value_nulls = take_nulls(values.nulls(), indices);
+    let value_nulls = take_nulls::<_, CHECKED>(values.nulls(), indices);
     let final_nulls = NullBuffer::union(value_nulls.as_ref(), indices.nulls());
 
     return FixedSizeBinaryArray::try_new(size, result_buffer, final_nulls);
@@ -928,11 +928,11 @@ fn take_fixed_size<IndexType: ArrowPrimitiveType, const 
N: usize>(
 ///
 /// applies `take` to the keys of the dictionary array and returns a new 
dictionary array
 /// with the same dictionary values and reordered keys
-fn take_dict<T: ArrowDictionaryKeyType, I: ArrowPrimitiveType>(
+fn take_dict<T: ArrowDictionaryKeyType, I: ArrowPrimitiveType, const CHECKED: 
bool>(
     values: &DictionaryArray<T>,
     indices: &PrimitiveArray<I>,
 ) -> Result<DictionaryArray<T>, ArrowError> {
-    let new_keys = take_primitive(values.keys(), indices)?;
+    let new_keys = take_primitive::<_, _, CHECKED>(values.keys(), indices)?;
     Ok(unsafe { DictionaryArray::new_unchecked(new_keys, 
values.values().clone()) })
 }
 
@@ -2081,7 +2081,8 @@ mod tests {
 
         let input_array = FixedSizeListArray::from_iter_primitive::<T, _, 
_>(input_data, length);
 
-        let output = take_fixed_size_list(&input_array, &indices, length as 
u32).unwrap();
+        let output =
+            take_fixed_size_list::<_, true>(&input_array, &indices, length as 
u32).unwrap();
 
         let expected = FixedSizeListArray::from_iter_primitive::<T, _, 
_>(expected_data, length);
 
@@ -2257,7 +2258,7 @@ mod tests {
         // The two middle indices are null -> Should be null in the output.
         let indices = UInt32Array::from(vec![Some(0), None, None, Some(3)]);
 
-        let result = take_fixed_size_binary(&fsb, &indices, 4).unwrap();
+        let result = take_fixed_size_binary::<_, true>(&fsb, &indices, 
4).unwrap();
         assert_eq!(result.len(), 4);
         assert_eq!(result.null_count(), 2);
         assert_eq!(
@@ -2286,7 +2287,7 @@ mod tests {
         // The two middle indices are null -> Should be null in the output.
         let indices = UInt32Array::from(vec![Some(0), None, None, Some(3)]);
 
-        let result = take_fixed_size_binary(&fsb, &indices, 5).unwrap();
+        let result = take_fixed_size_binary::<_, true>(&fsb, &indices, 
5).unwrap();
         assert_eq!(result.len(), 4);
         assert_eq!(result.null_count(), 2);
         assert_eq!(
@@ -2918,7 +2919,8 @@ mod tests {
 
         let logical_indices: PrimitiveArray<Int32Type> = 
PrimitiveArray::from(Vec::<i32>::new());
 
-        let result = take_impl(&run_array, &logical_indices).expect("take_run 
with empty indices");
+        let result = take_impl::<_, true>(&run_array, &logical_indices)
+            .expect("take_run with empty indices");
 
         // Verify the result is a valid empty RunArray
         assert_eq!(result.len(), 0);

Reply via email to