This is an automated email from the ASF dual-hosted git repository.
dheres pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/master by this push:
new 5de1d5e741 Optimize `max_boolean` by operating on u64 chunks (#6098)
5de1d5e741 is described below
commit 5de1d5e74170902224c715a8ef3bb595abb069b3
Author: Simon Vandel Sillesen <[email protected]>
AuthorDate: Mon Jul 22 17:17:49 2024 +0000
Optimize `max_boolean` by operating on u64 chunks (#6098)
* Optimize `max_boolean`
Operate on bit chunks instead of individual booleans, which can lead to
massive speedups while not regressing the short-circuiting behavior of the
existing implementation.
`cargo bench --bench aggregate_kernels -- "bool/max"` shows throughput
improvements between 50% to 23390% on my machine.
* add tests exercising u64 chunk code
---
arrow-arith/src/aggregate.rs | 120 +++++++++++++++++++++++++++++++++++--
arrow/benches/aggregate_kernels.rs | 47 +++++++++++++++
2 files changed, 162 insertions(+), 5 deletions(-)
diff --git a/arrow-arith/src/aggregate.rs b/arrow-arith/src/aggregate.rs
index 0e4d31eee7..ae4a5b008a 100644
--- a/arrow-arith/src/aggregate.rs
+++ b/arrow-arith/src/aggregate.rs
@@ -371,11 +371,26 @@ pub fn max_boolean(array: &BooleanArray) -> Option<bool> {
}
// Note the max bool is true (1), so short circuit as soon as we see it
- array
- .iter()
- .find(|&b| b == Some(true))
- .flatten()
- .or(Some(false))
+ match array.nulls() {
+ None => array
+ .values()
+ .bit_chunks()
+ .iter_padded()
+ // We found a true if any bit is set
+ .map(|x| x != 0)
+ .find(|b| *b)
+ .or(Some(false)),
+ Some(nulls) => {
+ let validity_chunks = nulls.inner().bit_chunks().iter_padded();
+ let value_chunks = array.values().bit_chunks().iter_padded();
+ value_chunks
+ .zip(validity_chunks)
+ // We found a true if the value bit is 1, AND the validity bit
is 1 for any bits in the chunk
+ .map(|(value_bits, validity_bits)| (value_bits &
validity_bits) != 0)
+ .find(|b| *b)
+ .or(Some(false))
+ }
+ }
}
/// Helper to compute min/max of [`ArrayAccessor`].
@@ -748,6 +763,7 @@ where
mod tests {
use super::*;
use arrow_array::types::*;
+ use builder::BooleanBuilder;
use std::sync::Arc;
#[test]
@@ -1318,6 +1334,14 @@ mod tests {
let a = BooleanArray::from(vec![Some(false), Some(true), None,
Some(false), None]);
assert_eq!(Some(false), min_boolean(&a));
assert_eq!(Some(true), max_boolean(&a));
+
+ let a = BooleanArray::from(vec![Some(true), None]);
+ assert_eq!(Some(true), min_boolean(&a));
+ assert_eq!(Some(true), max_boolean(&a));
+
+ let a = BooleanArray::from(vec![Some(false), None]);
+ assert_eq!(Some(false), min_boolean(&a));
+ assert_eq!(Some(false), max_boolean(&a));
}
#[test]
@@ -1339,6 +1363,92 @@ mod tests {
assert_eq!(Some(true), max_boolean(&a));
}
+ #[test]
+ fn test_boolean_min_max_64_true_64_false() {
+ let mut no_nulls = BooleanBuilder::new();
+ no_nulls.append_slice(&[true; 64]);
+ no_nulls.append_slice(&[false; 64]);
+ let no_nulls = no_nulls.finish();
+
+ assert_eq!(Some(false), min_boolean(&no_nulls));
+ assert_eq!(Some(true), max_boolean(&no_nulls));
+
+ let mut with_nulls = BooleanBuilder::new();
+ with_nulls.append_slice(&[true; 31]);
+ with_nulls.append_null();
+ with_nulls.append_slice(&[true; 32]);
+ with_nulls.append_slice(&[false; 1]);
+ with_nulls.append_nulls(63);
+ let with_nulls = with_nulls.finish();
+
+ assert_eq!(Some(false), min_boolean(&with_nulls));
+ assert_eq!(Some(true), max_boolean(&with_nulls));
+ }
+
+ #[test]
+ fn test_boolean_min_max_64_false_64_true() {
+ let mut no_nulls = BooleanBuilder::new();
+ no_nulls.append_slice(&[false; 64]);
+ no_nulls.append_slice(&[true; 64]);
+ let no_nulls = no_nulls.finish();
+
+ assert_eq!(Some(false), min_boolean(&no_nulls));
+ assert_eq!(Some(true), max_boolean(&no_nulls));
+
+ let mut with_nulls = BooleanBuilder::new();
+ with_nulls.append_slice(&[false; 31]);
+ with_nulls.append_null();
+ with_nulls.append_slice(&[false; 32]);
+ with_nulls.append_slice(&[true; 1]);
+ with_nulls.append_nulls(63);
+ let with_nulls = with_nulls.finish();
+
+ assert_eq!(Some(false), min_boolean(&with_nulls));
+ assert_eq!(Some(true), max_boolean(&with_nulls));
+ }
+
+ #[test]
+ fn test_boolean_min_max_96_true() {
+ let mut no_nulls = BooleanBuilder::new();
+ no_nulls.append_slice(&[true; 96]);
+ let no_nulls = no_nulls.finish();
+
+ assert_eq!(Some(true), min_boolean(&no_nulls));
+ assert_eq!(Some(true), max_boolean(&no_nulls));
+
+ let mut with_nulls = BooleanBuilder::new();
+ with_nulls.append_slice(&[true; 31]);
+ with_nulls.append_null();
+ with_nulls.append_slice(&[true; 32]);
+ with_nulls.append_slice(&[true; 31]);
+ with_nulls.append_null();
+ let with_nulls = with_nulls.finish();
+
+ assert_eq!(Some(true), min_boolean(&with_nulls));
+ assert_eq!(Some(true), max_boolean(&with_nulls));
+ }
+
+ #[test]
+ fn test_boolean_min_max_96_false() {
+ let mut no_nulls = BooleanBuilder::new();
+ no_nulls.append_slice(&[false; 96]);
+ let no_nulls = no_nulls.finish();
+
+ assert_eq!(Some(false), min_boolean(&no_nulls));
+ assert_eq!(Some(false), max_boolean(&no_nulls));
+
+ let mut with_nulls = BooleanBuilder::new();
+ with_nulls.append_slice(&[false; 31]);
+ with_nulls.append_null();
+ with_nulls.append_slice(&[false; 32]);
+ with_nulls.append_slice(&[false; 31]);
+ with_nulls.append_null();
+ let with_nulls = with_nulls.finish();
+
+ assert_eq!(Some(false), min_boolean(&with_nulls));
+ assert_eq!(Some(false), max_boolean(&with_nulls));
+ }
+
#[test]
fn test_sum_dyn() {
let values = Int8Array::from_iter_values([10_i8, 11, 12, 13, 14, 15,
16, 17]);
diff --git a/arrow/benches/aggregate_kernels.rs
b/arrow/benches/aggregate_kernels.rs
index 1e7b9f894f..71cd242784 100644
--- a/arrow/benches/aggregate_kernels.rs
+++ b/arrow/benches/aggregate_kernels.rs
@@ -66,6 +66,53 @@ fn add_benchmark(c: &mut Criterion) {
.bench_function("min nullable", |b| b.iter(||
min_string(&nullable_strings)))
.bench_function("max nullable", |b| b.iter(||
max_string(&nullable_strings)));
}
+
+ {
+ let nonnull_bools_mixed = create_boolean_array(BATCH_SIZE, 0.0, 0.5);
+ let nonnull_bools_all_false = create_boolean_array(BATCH_SIZE, 0.0,
0.0);
+ let nonnull_bools_all_true = create_boolean_array(BATCH_SIZE, 0.0,
1.0);
+ let nullable_bool_mixed = create_boolean_array(BATCH_SIZE, 0.5, 0.5);
+ let nullable_bool_all_false = create_boolean_array(BATCH_SIZE, 0.5,
0.0);
+ let nullable_bool_all_true = create_boolean_array(BATCH_SIZE, 0.5,
1.0);
+ c.benchmark_group("bool")
+ .throughput(Throughput::Elements(BATCH_SIZE as u64))
+ .bench_function("min nonnull mixed", |b| {
+ b.iter(|| min_boolean(&nonnull_bools_mixed))
+ })
+ .bench_function("max nonnull mixed", |b| {
+ b.iter(|| max_boolean(&nonnull_bools_mixed))
+ })
+ .bench_function("min nonnull false", |b| {
+ b.iter(|| min_boolean(&nonnull_bools_all_false))
+ })
+ .bench_function("max nonnull false", |b| {
+ b.iter(|| max_boolean(&nonnull_bools_all_false))
+ })
+ .bench_function("min nonnull true", |b| {
+ b.iter(|| min_boolean(&nonnull_bools_all_true))
+ })
+ .bench_function("max nonnull true", |b| {
+ b.iter(|| max_boolean(&nonnull_bools_all_true))
+ })
+ .bench_function("min nullable mixed", |b| {
+ b.iter(|| min_boolean(&nullable_bool_mixed))
+ })
+ .bench_function("max nullable mixed", |b| {
+ b.iter(|| max_boolean(&nullable_bool_mixed))
+ })
+ .bench_function("min nullable false", |b| {
+ b.iter(|| min_boolean(&nullable_bool_all_false))
+ })
+ .bench_function("max nullable false", |b| {
+ b.iter(|| max_boolean(&nullable_bool_all_false))
+ })
+ .bench_function("min nullable true", |b| {
+ b.iter(|| min_boolean(&nullable_bool_all_true))
+ })
+ .bench_function("max nullable true", |b| {
+ b.iter(|| max_boolean(&nullable_bool_all_true))
+ });
+ }
}
criterion_group!(benches, add_benchmark);