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-11992-76139438ddd6f43159e4bf013d22238b4c82670b
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git

commit 9ffd63d528e924d9c0faceef0027e3766b615c98
Author: Neil Conway <[email protected]>
AuthorDate: Thu Oct 29 19:02:38 2026 +0000

    perf: optimize building out-of-line views in `MutableArrayData` (#11992)
    
    # Which issue does this PR close?
    
    - Split out from #11656
    - Related to #11770
    
    # Rationale for this change
    
    This PR applies the optimization from #11770 to an additional code path:
    when `MutableArrayData` copies string views into an array that already
    has data buffers, the buffer index in the copied views must be updated.
    We previously did that by unpacking the raw view (`u128` -> `ByteView`),
    adding the buffer index offset, and repacking the view. It is faster to
    do this by using bitwise shifts and addition directly. This also avoids
    a data-dependent branch, which seems to have been poorly predicted on
    x86.
    
    Benchmarks:
    
    x86 (AMD EPYC Milan):
    
    - zip_kernels, string views, 8192 rows of 3 to 19 bytes
      - array vs scalar
        - all_true: 31.19 µs → 6.41 µs (−79.4%)
        - 99pct_true: 35.84 µs → 10.94 µs (−69.5%)
        - 90pct_true: 63.87 µs → 39.54 µs (−38.1%)
        - 50pct_true: 129.4 µs → 113.1 µs (−12.6%)
        - true_then_false: 55.02 µs → 43.06 µs (−21.7%)
        - 10pct_true: 96.03 µs → 92.53 µs (−3.6%)
        - 1pct_true: 81.66 µs → 82.20 µs (+0.7%)
        - all_false: 78.78 µs → 81.76 µs (+3.8%) (runs 6.7% apart)
        - 50pct_nulls: 112.6 µs → 107.5 µs (−4.5%) (runs 6.4% apart)
      - scalar vs array
        - all_true: 82.50 µs → 85.26 µs (+3.3%) (runs 4.4% apart)
        - 99pct_true: 86.52 µs → 87.81 µs (+1.5%) (runs 4.4% apart)
        - 90pct_true: 100.9 µs → 112 µs (+11.0%) (runs 31.5% apart)
        - 50pct_true: 136.6 µs → 121 µs (−11.4%) (runs 5.6% apart)
        - true_then_false: 58.65 µs → 47.93 µs (−18.3%) (runs 7.2% apart)
        - 10pct_true: 69.18 µs → 44.68 µs (−35.4%) (runs 5.1% apart)
        - 1pct_true: 39.66 µs → 15.05 µs (−62.1%) (runs 18.5% apart)
        - all_false: 30.34 µs → 7.15 µs (−76.4%) (runs 17.9% apart)
        - 50pct_nulls: 102.3 µs → 81.27 µs (−20.6%) (runs 8.6% apart)
      - array vs array
        - all_true: 30.94 µs → 6.82 µs (−77.9%) (runs 2.6% apart)
        - 99pct_true: 37.47 µs → 12.13 µs (−67.6%) (runs 2.8% apart)
        - 90pct_true: 71.25 µs → 45.33 µs (−36.4%) (runs 13.0% apart)
        - 50pct_true: 77.46 µs → 79.37 µs (+2.5%) (runs 2.9% apart)
        - true_then_false: 31.60 µs → 7.00 µs (−77.9%) (runs 3.5% apart)
        - 10pct_true: 72.32 µs → 47.72 µs (−34.0%) (runs 25.3% apart)
        - 1pct_true: 37.44 µs → 12.04 µs (−67.8%) (runs 5.9% apart)
        - all_false: 31.26 µs → 7.01 µs (−77.6%) (runs 4.9% apart)
        - 50pct_nulls: 77.66 µs → 78.57 µs (+1.2%)
    - mutable_array
      - mutable str_view 1024: 294.5 µs → 267.7 µs (−9.1%)
    - mutable str_view short slices 1024: 19.84 µs → 17.69 µs (−10.9%) (runs
    4.3% apart)
    
    ARM (M4 Max):
    
    - zip_kernels, string views, 8192 rows of 3 to 19 bytes
      - array vs scalar
        - all_true: 4.27 µs → 3.55 µs (−16.9%) (runs 2.9% apart)
        - 99pct_true: 7.22 µs → 6.61 µs (−8.4%) (runs 5.6% apart)
        - 90pct_true: 23.69 µs → 22.70 µs (−4.2%)
        - 50pct_true: 63.98 µs → 64.23 µs (+0.4%)
        - true_then_false: 25.21 µs → 25.53 µs (+1.3%) (runs 3.9% apart)
        - 10pct_true: 53.92 µs → 56.04 µs (+3.9%) (runs 6.1% apart)
        - 1pct_true: 49.47 µs → 51.00 µs (+3.1%) (runs 4.0% apart)
        - all_false: 45.95 µs → 46.10 µs (+0.3%)
        - 50pct_nulls: 60.34 µs → 60.33 µs (−0.0%)
      - scalar vs array
        - all_true: 49.63 µs → 50.10 µs (+0.9%)
        - 99pct_true: 53.14 µs → 53.63 µs (+0.9%)
        - 90pct_true: 57.90 µs → 58.62 µs (+1.2%)
        - 50pct_true: 68.40 µs → 68.32 µs (−0.1%)
        - true_then_false: 28.06 µs → 28.33 µs (+1.0%) (runs 3.7% apart)
        - 10pct_true: 27.70 µs → 27.16 µs (−2.0%) (runs 3.4% apart)
        - 1pct_true: 10.63 µs → 10.11 µs (−4.9%) (runs 6.1% apart)
        - all_false: 4.22 µs → 3.58 µs (−15.3%) (runs 6.1% apart)
        - 50pct_nulls: 47.07 µs → 46.42 µs (−1.4%) (runs 2.7% apart)
      - array vs array
        - all_true: 4.40 µs → 3.59 µs (−18.3%)
        - 99pct_true: 7.51 µs → 6.97 µs (−7.2%) (runs 7.4% apart)
        - 90pct_true: 24.69 µs → 24.15 µs (−2.2%) (runs 7.6% apart)
        - 50pct_true: 49.95 µs → 51.71 µs (+3.5%) (runs 11.4% apart)
        - true_then_false: 4.50 µs → 3.64 µs (−18.9%)
        - 10pct_true: 24.68 µs → 23.40 µs (−5.2%)
        - 1pct_true: 7.63 µs → 6.79 µs (−11.1%) (runs 3.1% apart)
        - all_false: 4.38 µs → 3.60 µs (−17.7%) (runs 2.2% apart)
        - 50pct_nulls: 49.16 µs → 50.45 µs (+2.6%) (runs 4.2% apart)
    - mutable_array
    - mutable str_view 1024: 186.4 µs → 156.2 µs (−16.2%) (runs 6.3% apart)
    - mutable str_view short slices 1024: 10.49 µs → 10.04 µs (−4.3%) (runs
    4.7% apart)
    
    
    # What changes are included in this PR?
    
    See above.
    
    # Are these changes tested?
    
    Yes. `test_string_view` now covers strings of 12 and 13 bytes, on either
    side of the inline limit, copied from the second input.
    
    # Are there any significant user-facing changes?
    
    No.
---
 arrow-data/src/transform/mod.rs | 22 ++++++++++++++--------
 arrow/tests/array_transform.rs  | 14 ++++++--------
 2 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/arrow-data/src/transform/mod.rs b/arrow-data/src/transform/mod.rs
index cc6c20b2fd..ba9ad364c8 100644
--- a/arrow-data/src/transform/mod.rs
+++ b/arrow-data/src/transform/mod.rs
@@ -20,7 +20,7 @@
 //! Provides utilities for creating, manipulating, and converting Arrow arrays
 //! made of primitive types, strings, and nested types.
 
-use super::{ArrayData, ArrayDataBuilder, ByteView, data::new_buffers};
+use super::{ArrayData, ArrayDataBuilder, MAX_INLINE_VIEW_LEN, 
data::new_buffers};
 use crate::bit_mask::set_bits;
 use arrow_buffer::buffer::{BooleanBuffer, NullBuffer};
 use arrow_buffer::{
@@ -228,13 +228,19 @@ fn build_extend_view(array: &ArrayData, buffer_offset: 
u32) -> Extend<'_> {
             mutable
                 .buffer1
                 .extend(views[start..start + len].iter().map(|v| {
-                    let len = *v as u32;
-                    if len <= 12 {
-                        return *v; // Stored inline
-                    }
-                    let mut view = ByteView::from(*v);
-                    view.buffer_index += buffer_offset;
-                    view.into()
+                    // A view of a string of 12 bytes or fewer stores the
+                    // string inline and has no buffer index, so adding 0
+                    // leaves it unchanged.
+                    let offset = if *v as u32 > MAX_INLINE_VIEW_LEN {
+                        buffer_offset
+                    } else {
+                        0
+                    };
+                    // The buffer index is bits 64 to 95 of a view. Adding
+                    // `buffer_offset` gives the string's buffer index in the
+                    // output, which fits in 32 bits, so the sum never carries
+                    // into bit 96.
+                    v + ((offset as u128) << 64)
                 }));
             Ok(())
         },
diff --git a/arrow/tests/array_transform.rs b/arrow/tests/array_transform.rs
index 287fa684a3..17d515059c 100644
--- a/arrow/tests/array_transform.rs
+++ b/arrow/tests/array_transform.rs
@@ -1065,12 +1065,10 @@ fn test_extend_nulls_not_nullable() {
 fn test_string_view() {
     let a1 =
         StringViewArray::from(vec!["foo", "very long string over 12 bytes", 
"bar"]).into_data();
-    let a2 = StringViewArray::from_iter(vec![
-        Some("bar"),
-        None,
-        Some("long string also over 12 bytes"),
-    ])
-    .into_data();
+    // Strings of 12 bytes are stored in their views and strings of 13 bytes
+    // are not.
+    let a2 = StringViewArray::from_iter(vec![Some("twelve bytes"), None, 
Some("thirteen byte")])
+        .into_data();
 
     a1.validate_full().unwrap();
     a2.validate_full().unwrap();
@@ -1091,10 +1089,10 @@ fn test_string_view() {
     assert_eq!(
         v,
         vec![
-            Some("bar"),
+            Some("twelve bytes"),
             Some("very long string over 12 bytes"),
             Some("foo"),
-            Some("long string also over 12 bytes")
+            Some("thirteen byte")
         ]
     )
 }

Reply via email to