Repository: pola-rs/polars
File: crates/polars-compute/src/cast/binview_to.rs (Rust)

Code before:
        from,
        to,
        is_little_endian,
    )))
}

/// Casts a [`BinaryArray`] to a [`FixedSizeListArray`], making any un-castable value a Null.
///
/// # Panics
///    Panics if `to` is not `ArrowDataType::FixedSizeList`.
pub(super) fn try_binview_to_fixed_size_list<T>(
    from: &BinaryViewArray,
    to: &ArrowDataType,
    is_little_endian: bool,
) -> PolarsResult<FixedSizeListArray>
where
    T: FromBytes + NativeType,
    for<'a> &'a <T as FromBytes>::Bytes: TryFrom<&'a [u8]>,
{
    let ArrowDataType::FixedSizeList(_, array_size) = to else {
        // Higher-level code should have ensured we only get FixedSizeList:
        unreachable!();
    };
    let element_size = std::mem::size_of::<T>();
    let array_size = *array_size;
    let mut result = MutableFixedSizeListArray::new(
        MutablePrimitiveArray::<T>::with_capacity(from.len() * array_size),
        array_size,
    );

    // It would be nice to have a fast path that just use a memory copy.
    // However, that is only possible if all the binaryviews are the correct
    // length, and checking that requires iterating over all of them, so it's
    // not obvious that would actually be much of a speed up.
    from.iter().try_for_each(|x| {
        if let Some(x) = x {
            if x.len() != element_size * array_size {
                result.push_null();
                return Ok(());
            }

            result.try_push(Some(x.chunks_exact(element_size).map(|val| {
                if is_little_endian {
                    Some(<T as FromBytes>::from_le_bytes(val.try_into().ok()?))
                } else {
                    Some(<T as FromBytes>::from_be_bytes(val.try_into().ok()?))
                }
            })))
        } else {
            result.push_null();
            Ok(())

Review comment (performance):
Please implement this by directly constructing `Vec<T>` instead of using the array builders.

The array builders have some extra fluff that aren't needed due to the invariants in this context. In particular they have inner validity handling that is simply unneeded since we never have inner NULLs.

Note that the outer validity will need to be updated properly from within the loop.

I'm roughly expecting to see these points in the function:

```rust
let mut out = Vec::with_capacity(output_length)

for .. {
    // This should assign the value using raw pointers rather than `push()` or `extend()` - those functions check for capacity / update the length, which is not needed since we've allocated properly upfront.

    // If the length does not match then the outer validity should be set to NULL.
}

out.set_len(output_length)

return (PrimitiveArray::<T>::from_vec(out), validity)
```

Please also manually write out the loops instead of using `chunks_exact()` - that function has remainder handling which we simply don't need since we check the length upfront - it can unnecessarily make it harder for the compiler to perform optimizations.

In general, at this level of code it's desirable to avoid calling into functions unless you absolutely have to, or that it saves a large amount of complexity. Loops in particular should just be written in plain form to maximize auto-vectorization.


Code after:
        from,
        to,
        is_little_endian,
    )))
}

/// Casts a [`BinaryViewArray`] to a [`FixedSizeListArray`], making any un-castable value a Null.
///
/// # Arguments
///
/// * `from`: The array to reinterpret.
/// * `array_width`: The number of items in each `Array`.
pub(super) fn try_binview_to_fixed_size_list<T, const IS_LITTLE_ENDIAN: bool>(
    from: &BinaryViewArray,
    array_width: usize,
) -> PolarsResult<FixedSizeListArray>
where
    T: FromBytes + NativeType,
    for<'a> &'a <T as FromBytes>::Bytes: TryFrom<&'a [u8]>,
{
    let element_size = std::mem::size_of::<T>();
    // The maximum number of primitives in the result:
    let primitive_length = from.len().checked_mul(array_width).ok_or_else(|| {
        polars_err!(
            InvalidOperation:
            "array chunk length * number of items ({} * {}) is too large",
            from.len(),
            array_width
        )
    })?;
    // The size of each array, in bytes:
    let row_size_bytes = element_size.checked_mul(array_width).ok_or_else(|| {
        polars_err!(
            InvalidOperation:
            "array size in bytes ({} * {}) is too large",
            element_size,
            array_width
        )
    })?;

    let mut out: Vec<T> = vec![T::zeroed(); primitive_length];
    let (out_u8_ptr, out_len_bytes) = {
        let out_u8_slice = cast_slice_mut::<_, u8>(out.as_mut());
        (out_u8_slice.as_mut_ptr(), out_u8_slice.len())
    };
    assert_eq!(out_len_bytes, row_size_bytes * from.len());
    let mut validity = MutableBitmap::from_len_set(from.len());

    for (index, value) in from.iter().enumerate() {
        if let Some(value) = value
            && value.len() == row_size_bytes