You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/04/27 17:07:50 UTC

(datafusion) 01/01: perf: Use bulk-NULL builder in `chr` (#21847)

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

github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-21847-26762977499c3ea581a8c3f698c855c4d607a2c6
in repository https://gitbox.apache.org/repos/asf/datafusion.git

commit 62ad66beb1e13490980dc5e53e5542f6f07cce65
Author: Neil Conway <ne...@gmail.com>
AuthorDate: Mon Apr 27 13:07:27 2026 -0400

    perf: Use bulk-NULL builder in `chr` (#21847)
    
    ## Which issue does this PR close?
    
    - Closes #21846.
    
    ## Rationale for this change
    
    Optimize `chr` by avoiding per-row NULL bitmap maintenance, and also
    split the hot loop to avoid taking a branch when no NULL bitmap is
    given.
    
    Benchmarks:
    
    ```
    chr/array: 3.8768 µs → 3.1548 µs, −18.57% (`p < 0.05`)
    ```
    
    ## What changes are included in this PR?
    
    * Optimize `chr` to reduce NULL-handling overhead
    
    ## Are these changes tested?
    
    Yes.
    
    ## Are there any user-facing changes?
    
    No.
    
    ---------
    
    Co-authored-by: Andrew Lamb <an...@nerdnetworks.org>
---
 datafusion/functions/src/string/chr.rs | 58 +++++++++++++++++++++++-----------
 1 file changed, 40 insertions(+), 18 deletions(-)

diff --git a/datafusion/functions/src/string/chr.rs b/datafusion/functions/src/string/chr.rs
index 058bbe10b2..60df0c47cf 100644
--- a/datafusion/functions/src/string/chr.rs
+++ b/datafusion/functions/src/string/chr.rs
@@ -17,11 +17,12 @@
 
 use std::sync::Arc;
 
-use arrow::array::{ArrayRef, GenericStringBuilder, Int64Array};
+use arrow::array::{Array, ArrayRef, Int64Array};
 use arrow::datatypes::DataType;
 use arrow::datatypes::DataType::Int64;
 use arrow::datatypes::DataType::Utf8;
 
+use crate::strings::GenericStringArrayBuilder;
 use datafusion_common::cast::as_int64_array;
 use datafusion_common::utils::take_function_args;
 use datafusion_common::{Result, ScalarValue, exec_err, internal_err};
@@ -32,31 +33,46 @@ use datafusion_macros::user_doc;
 /// Returns the character with the given code.
 /// chr(65) = 'A'
 fn chr_array(integer_array: &Int64Array) -> Result<ArrayRef> {
-    let mut builder = GenericStringBuilder::<i32>::with_capacity(
-        integer_array.len(),
-        // 1 byte per character, assuming that is the common case
-        integer_array.len(),
+    let len = integer_array.len();
+    let mut builder = GenericStringArrayBuilder::<i32>::with_capacity(
+        len, // 1 byte per character, assuming that is the common case
+        len,
     );
 
     let mut buf = [0u8; 4];
+    let nulls = integer_array.nulls();
 
-    for integer in integer_array {
-        match integer {
-            Some(integer) => {
-                if let Ok(u) = u32::try_from(integer)
-                    && let Some(c) = core::char::from_u32(u)
-                {
-                    builder.append_value(c.encode_utf8(&mut buf));
-                    continue;
-                }
-
-                return exec_err!("invalid Unicode scalar value: {integer}");
+    if let Some(n) = nulls {
+        for i in 0..len {
+            if n.is_null(i) {
+                builder.append_placeholder();
+                continue;
             }
-            None => builder.append_null(),
+            // SAFETY: bounds + null check above.
+            let integer = unsafe { integer_array.value_unchecked(i) };
+            if let Ok(u) = u32::try_from(integer)
+                && let Some(c) = core::char::from_u32(u)
+            {
+                builder.append_value(c.encode_utf8(&mut buf));
+                continue;
+            }
+            return exec_err!("invalid Unicode scalar value: {integer}");
+        }
+    } else {
+        for i in 0..len {
+            // SAFETY: no null buffer means every index is valid.
+            let integer = unsafe { integer_array.value_unchecked(i) };
+            if let Ok(u) = u32::try_from(integer)
+                && let Some(c) = core::char::from_u32(u)
+            {
+                builder.append_value(c.encode_utf8(&mut buf));
+                continue;
+            }
+            return exec_err!("invalid Unicode scalar value: {integer}");
         }
     }
 
-    Ok(Arc::new(builder.finish()) as ArrayRef)
+    Ok(Arc::new(builder.finish(nulls.cloned())?) as ArrayRef)
 }
 
 #[user_doc(
@@ -198,7 +214,13 @@ mod tests {
         ];
 
         assert_eq!(string_array.len(), expected.len());
+        assert_eq!(string_array.null_count(), 1);
+        assert!(string_array.is_null(7));
         for (i, e) in expected.iter().enumerate() {
+            if i == 7 {
+                continue;
+            }
+            assert!(!string_array.is_null(i));
             assert_eq!(string_array.value(i), *e);
         }
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org