Skip to content

Commit 9ffd63d

Browse files
authored
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.
1 parent 7613943 commit 9ffd63d

2 files changed

Lines changed: 20 additions & 16 deletions

File tree

‎arrow-data/src/transform/mod.rs‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
//! Provides utilities for creating, manipulating, and converting Arrow arrays
2121
//! made of primitive types, strings, and nested types.
2222
23-
use super::{ArrayData, ArrayDataBuilder, ByteView, data::new_buffers};
23+
use super::{ArrayData, ArrayDataBuilder, MAX_INLINE_VIEW_LEN, data::new_buffers};
2424
use crate::bit_mask::set_bits;
2525
use arrow_buffer::buffer::{BooleanBuffer, NullBuffer};
2626
use arrow_buffer::{
@@ -228,13 +228,19 @@ fn build_extend_view(array: &ArrayData, buffer_offset: u32) -> Extend<'_> {
228228
mutable
229229
.buffer1
230230
.extend(views[start..start + len].iter().map(|v| {
231-
let len = *v as u32;
232-
if len <= 12 {
233-
return *v; // Stored inline
234-
}
235-
let mut view = ByteView::from(*v);
236-
view.buffer_index += buffer_offset;
237-
view.into()
231+
// A view of a string of 12 bytes or fewer stores the
232+
// string inline and has no buffer index, so adding 0
233+
// leaves it unchanged.
234+
let offset = if *v as u32 > MAX_INLINE_VIEW_LEN {
235+
buffer_offset
236+
} else {
237+
0
238+
};
239+
// The buffer index is bits 64 to 95 of a view. Adding
240+
// `buffer_offset` gives the string's buffer index in the
241+
// output, which fits in 32 bits, so the sum never carries
242+
// into bit 96.
243+
v + ((offset as u128) << 64)
238244
}));
239245
Ok(())
240246
},

‎arrow/tests/array_transform.rs‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,12 +1065,10 @@ fn test_extend_nulls_not_nullable() {
10651065
fn test_string_view() {
10661066
let a1 =
10671067
StringViewArray::from(vec!["foo", "very long string over 12 bytes", "bar"]).into_data();
1068-
let a2 = StringViewArray::from_iter(vec![
1069-
Some("bar"),
1070-
None,
1071-
Some("long string also over 12 bytes"),
1072-
])
1073-
.into_data();
1068+
// Strings of 12 bytes are stored in their views and strings of 13 bytes
1069+
// are not.
1070+
let a2 = StringViewArray::from_iter(vec![Some("twelve bytes"), None, Some("thirteen byte")])
1071+
.into_data();
10741072

10751073
a1.validate_full().unwrap();
10761074
a2.validate_full().unwrap();
@@ -1091,10 +1089,10 @@ fn test_string_view() {
10911089
assert_eq!(
10921090
v,
10931091
vec![
1094-
Some("bar"),
1092+
Some("twelve bytes"),
10951093
Some("very long string over 12 bytes"),
10961094
Some("foo"),
1097-
Some("long string also over 12 bytes")
1095+
Some("thirteen byte")
10981096
]
10991097
)
11001098
}

0 commit comments

Comments
 (0)