mbutrovich commented on code in PR #26377:
URL: https://github.com/apache/datafusion/pull/26377#discussion_r4286027151


##########
datafusion/functions/src/strings.rs:
##########
@@ -829,38 +832,78 @@ impl StringViewArrayBuilder {
         Self::make_long_view_checked(length, buffer_index, offset, 
prefix_bytes)
     }
 
-    /// See [`BulkNullStringArrayBuilder::append_byte_map`].
+    /// Fallible variant of [`Self::append_byte_map`].
     ///
     /// # Safety
     ///
     /// The bytes produced by applying `map` to each byte of `src`, in order,
     /// must form valid UTF-8.
     ///
-    /// # Panics
+    /// # Errors
     ///
-    /// Panics under the same conditions as [`Self::append_value`]: if
-    /// `src.len()`, the in-progress buffer offset, or the number of completed
-    /// buffers exceeds `i32::MAX`.
+    /// Returns an error under the same conditions as
+    /// [`Self::try_append_value`].
     #[inline]
-    pub unsafe fn append_byte_map<F: FnMut(u8) -> u8>(&mut self, src: &[u8], 
mut map: F) {
-        let length: u32 =
-            i32::try_from(src.len()).expect("value length exceeds i32::MAX") 
as u32;
-        if length <= 12 {
-            let mut bytes = [0u8; 12];
-            for (d, &b) in bytes[..src.len()].iter_mut().zip(src) {
+    pub unsafe fn try_append_byte_map<F: FnMut(u8) -> u8>(
+        &mut self,
+        src: &[u8],
+        mut map: F,
+    ) -> Result<()> {
+        if src.len() <= MAX_INLINE_LEN {
+            // An inline view holds the length in its low 4 bytes, followed by
+            // the value zero-padded to 12 bytes. Writing the mapped bytes
+            // straight into it is faster than going through `inline_view`.
+            let mut view = (src.len() as u128).to_le_bytes();
+            for (d, &b) in view[4..].iter_mut().zip(src) {
                 *d = map(b);
             }
-            self.views.push(make_view(&bytes[..src.len()], 0, 0));
-            return;
+            self.views.push(u128::from_le_bytes(view));
+            return Ok(());
         }
 
-        self.ensure_long_capacity(length);
+        let length: u32 = i32::try_from(src.len())
+            .map_err(|_| string_view_overflow_error("value length"))?
+            as u32;
+        self.try_ensure_long_capacity(length)?;
 
         let cursor = self.in_progress.len();
-        let offset: u32 = i32::try_from(cursor).expect("offset exceeds 
i32::MAX") as u32;
+        let offset: u32 = i32::try_from(cursor)
+            .map_err(|_| string_view_overflow_error("offset"))?
+            as u32;
+        let buffer_index: u32 = i32::try_from(self.completed.len())
+            .map_err(|_| string_view_overflow_error("buffer count"))?
+            as u32;
         self.in_progress.extend(src.iter().map(|&b| map(b)));

Review Comment:
   I checked the codegen for this branch. As I read it, `upper` and `lower` 
already get vector code here. I built the `string_expressions` bench binary 
(release profile with LTO) for `x86_64-apple-darwin` at the default target CPU. 
There, `append_case_converted_view` compiles 
`self.in_progress.extend(src.iter().map(..))` to an SSE2 loop that handles 32 
bytes per iteration:
   
   ```
   movdqu  xmm3, xmmword ptr [rdx + r10]        # load 32 source bytes
   movdqu  xmm4, xmmword ptr [rdx + r10 + 0x10]
   paddb   xmm5, xmm0                           # xmm0 = 0xbf, so b - 'A'
   pminub  xmm7, xmm1                           # xmm1 = 25
   pcmpeqb xmm7, xmm5                           # b is in 'A'..='Z'
   pand    xmm7, xmm2                           # xmm2 = 0x20
   por     xmm7, xmm3                           # set the lowercase bit
   movdqu  xmmword ptr [r9 + r10 - 0x10], xmm7  # store into in_progress
   ```
   
   The `upper` case is the same loop with `pxor`. The generic builder's new 
copy-then-map loop compiles to the same thing. On aarch64, both get a NEON loop 
that handles 64 bytes per iteration. As I read Arrow's 
[`MutableBuffer::extend_from_iter`](https://github.com/apache/arrow-rs/blob/ef1fa157977633f0ba21aa921f9ef3d5c669235b/arrow-buffer/src/buffer/mutable.rs#L1007-L1019),
 its loop runs until capacity is reached and checks `iterator.next()` on every 
step, so it has two exits. `Vec::extend` on a `TrustedLen` iterator reserves 
once and runs a counted loop. So copy-then-map helps the `MutableBuffer` 
builder and wouldn't change the `Vec` one. For `translate`, the map is a table 
lookup, which stays a scalar byte-at-a-time lookup (`movzx` loads, no xmm 
registers) in both builders on x86. That fits `translate` on `Utf8View` staying 
flat in your numbers. If that matches your reading, I think this thread can be 
resolved without a change.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to