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

alamb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/master by this push:
     new 6a13206  ARROW-11913: [Rust] Improve performance of StringBuilder by 
delaying bitmap creation
6a13206 is described below

commit 6a1320604a2021711e637c2ca586b374a593d66e
Author: Ilya Biryukov <[email protected]>
AuthorDate: Sat Mar 13 05:55:45 2021 -0500

    ARROW-11913: [Rust] Improve performance of StringBuilder by delaying bitmap 
creation
    
    `PrimitiveBuilder<u8>` was performing a lot of unnecessary bit operations.
    One operation for each byte of each string one appends.
    
    Delaying the creation of null bitmap builder for `PrimitiveBuilder`
    significantly improves performance of `StringBuilder`. The optimization is
    also generally useful as the null bitmap is not stored if resulting
    arrays do not contain nulls.
    
    Results from the added benchmark on my machine are:
    
    ```
    bench_primitive/bench_string
                            time:   [6.8971 ms 6.9123 ms 6.9335 ms]
                            thrpt:  [937.48 MiB/s 940.35 MiB/s 942.43 MiB/s]
                     change:
                            time:   [-49.585% -49.395% -49.204%] (p = 0.00 < 
0.05)
                            thrpt:  [+96.867% +97.610% +98.355%]
                            Performance has improved.
    ```
    
    Closes #9662 from ilya-biryukov/optstr
    
    Authored-by: Ilya Biryukov <[email protected]>
    Signed-off-by: Andrew Lamb <[email protected]>
---
 rust/arrow/benches/builder.rs   | 40 +++++++++++++++++++++++++++++-
 rust/arrow/src/array/builder.rs | 54 ++++++++++++++++++++++++++++++++---------
 2 files changed, 81 insertions(+), 13 deletions(-)

diff --git a/rust/arrow/benches/builder.rs b/rust/arrow/benches/builder.rs
index f1db508..fd9f319 100644
--- a/rust/arrow/benches/builder.rs
+++ b/rust/arrow/benches/builder.rs
@@ -51,6 +51,20 @@ fn bench_primitive(c: &mut Criterion) {
     group.finish();
 }
 
+fn bench_primitive_nulls(c: &mut Criterion) {
+    let mut group = c.benchmark_group("bench_primitive_nulls");
+    group.bench_function("bench_primitive_nulls", |b| {
+        b.iter(|| {
+            let mut builder = UInt8Builder::new(64);
+            for _ in 0..NUM_BATCHES * BATCH_SIZE {
+                let _ = black_box(builder.append_null());
+            }
+            black_box(builder.finish());
+        })
+    });
+    group.finish();
+}
+
 fn bench_bool(c: &mut Criterion) {
     let data: Vec<bool> = seedable_rng()
         .sample_iter(&Standard)
@@ -74,5 +88,29 @@ fn bench_bool(c: &mut Criterion) {
     group.finish();
 }
 
-criterion_group!(benches, bench_primitive, bench_bool);
+fn bench_string(c: &mut Criterion) {
+    const SAMPLE_STRING: &str = "sample string";
+    let mut group = c.benchmark_group("bench_primitive");
+    group.throughput(Throughput::Bytes(
+        ((BATCH_SIZE * NUM_BATCHES * SAMPLE_STRING.len()) as u32).into(),
+    ));
+    group.bench_function("bench_string", |b| {
+        b.iter(|| {
+            let mut builder = StringBuilder::new(64);
+            for _ in 0..NUM_BATCHES * BATCH_SIZE {
+                let _ = black_box(builder.append_value(SAMPLE_STRING));
+            }
+            black_box(builder.finish());
+        })
+    });
+    group.finish();
+}
+
+criterion_group!(
+    benches,
+    bench_primitive,
+    bench_primitive_nulls,
+    bench_bool,
+    bench_string
+);
 criterion_main!(benches);
diff --git a/rust/arrow/src/array/builder.rs b/rust/arrow/src/array/builder.rs
index 32eea9c..1ac679d 100644
--- a/rust/arrow/src/array/builder.rs
+++ b/rust/arrow/src/array/builder.rs
@@ -526,7 +526,9 @@ impl ArrayBuilder for BooleanBuilder {
 #[derive(Debug)]
 pub struct PrimitiveBuilder<T: ArrowPrimitiveType> {
     values_builder: BufferBuilder<T::Native>,
-    bitmap_builder: BooleanBufferBuilder,
+    /// We only materialize the builder when we add `false`.
+    /// This optimization is **very** important for performance of 
`StringBuilder`.
+    bitmap_builder: Option<BooleanBufferBuilder>,
 }
 
 impl<T: ArrowPrimitiveType> ArrayBuilder for PrimitiveBuilder<T> {
@@ -566,7 +568,7 @@ impl<T: ArrowPrimitiveType> PrimitiveBuilder<T> {
     pub fn new(capacity: usize) -> Self {
         Self {
             values_builder: BufferBuilder::<T::Native>::new(capacity),
-            bitmap_builder: BooleanBufferBuilder::new(capacity),
+            bitmap_builder: None,
         }
     }
 
@@ -577,14 +579,17 @@ impl<T: ArrowPrimitiveType> PrimitiveBuilder<T> {
 
     /// Appends a value of type `T` into the builder
     pub fn append_value(&mut self, v: T::Native) -> Result<()> {
-        self.bitmap_builder.append(true);
+        if let Some(b) = self.bitmap_builder.as_mut() {
+            b.append(true);
+        }
         self.values_builder.append(v);
         Ok(())
     }
 
     /// Appends a null slot into the builder
     pub fn append_null(&mut self) -> Result<()> {
-        self.bitmap_builder.append(false);
+        self.materialize_bitmap_builder();
+        self.bitmap_builder.as_mut().unwrap().append(false);
         self.values_builder.advance(1);
         Ok(())
     }
@@ -600,7 +605,9 @@ impl<T: ArrowPrimitiveType> PrimitiveBuilder<T> {
 
     /// Appends a slice of type `T` into the builder
     pub fn append_slice(&mut self, v: &[T::Native]) -> Result<()> {
-        self.bitmap_builder.append_n(v.len(), true);
+        if let Some(b) = self.bitmap_builder.as_mut() {
+            b.append_n(v.len(), true);
+        }
         self.values_builder.append_slice(v);
         Ok(())
     }
@@ -616,7 +623,12 @@ impl<T: ArrowPrimitiveType> PrimitiveBuilder<T> {
                 "Value and validity lengths must be equal".to_string(),
             ));
         }
-        self.bitmap_builder.append_slice(is_valid);
+        if is_valid.iter().any(|v| !*v) {
+            self.materialize_bitmap_builder();
+        }
+        if let Some(b) = self.bitmap_builder.as_mut() {
+            b.append_slice(is_valid);
+        }
         self.values_builder.append_slice(values);
         Ok(())
     }
@@ -624,13 +636,17 @@ impl<T: ArrowPrimitiveType> PrimitiveBuilder<T> {
     /// Builds the `PrimitiveArray` and reset this builder.
     pub fn finish(&mut self) -> PrimitiveArray<T> {
         let len = self.len();
-        let null_bit_buffer = self.bitmap_builder.finish();
-        let null_count = len - null_bit_buffer.count_set_bits();
+        let null_bit_buffer = self.bitmap_builder.as_mut().map(|b| b.finish());
+        let null_count = len
+            - null_bit_buffer
+                .as_ref()
+                .map(|b| b.count_set_bits())
+                .unwrap_or(len);
         let mut builder = ArrayData::builder(T::DATA_TYPE)
             .len(len)
             .add_buffer(self.values_builder.finish());
         if null_count > 0 {
-            builder = builder.null_bit_buffer(null_bit_buffer);
+            builder = builder.null_bit_buffer(null_bit_buffer.unwrap());
         }
         let data = builder.build();
         PrimitiveArray::<T>::from(data)
@@ -639,8 +655,12 @@ impl<T: ArrowPrimitiveType> PrimitiveBuilder<T> {
     /// Builds the `DictionaryArray` and reset this builder.
     pub fn finish_dict(&mut self, values: ArrayRef) -> DictionaryArray<T> {
         let len = self.len();
-        let null_bit_buffer = self.bitmap_builder.finish();
-        let null_count = len - null_bit_buffer.count_set_bits();
+        let null_bit_buffer = self.bitmap_builder.as_mut().map(|b| b.finish());
+        let null_count = len
+            - null_bit_buffer
+                .as_ref()
+                .map(|b| b.count_set_bits())
+                .unwrap_or(len);
         let data_type = DataType::Dictionary(
             Box::new(T::DATA_TYPE),
             Box::new(values.data_type().clone()),
@@ -649,11 +669,21 @@ impl<T: ArrowPrimitiveType> PrimitiveBuilder<T> {
             .len(len)
             .add_buffer(self.values_builder.finish());
         if null_count > 0 {
-            builder = builder.null_bit_buffer(null_bit_buffer);
+            builder = builder.null_bit_buffer(null_bit_buffer.unwrap());
         }
         builder = builder.add_child_data(values.data());
         DictionaryArray::<T>::from(builder.build())
     }
+
+    fn materialize_bitmap_builder(&mut self) {
+        if self.bitmap_builder.is_some() {
+            return;
+        }
+        let mut b = BooleanBufferBuilder::new(0);
+        b.reserve(self.values_builder.capacity());
+        b.append_n(self.values_builder.len, true);
+        self.bitmap_builder = Some(b);
+    }
 }
 
 ///  Array builder for `ListArray`

Reply via email to