Skip to content

Commit 7d9f620

Browse files
authored
Benchmarks and performance improvement for parquet boolean reader (#10196)
# Which issue does this PR close? <!-- We generally require a GitHub issue to be filed for all bug fixes and enhancements and this helps us generate change logs for our releases. You can link an issue to this PR using the GitHub syntax. --> - Closes #10195. # Rationale for this change Adds benchmarks and a performance improvements for reading boolean arrays from parquet. The optimized path for building a `Buffer` from `Vec<bool>` already existed, using it yields a ~25% performance improvement on the new benchmarks. <!-- Why are you proposing this change? If this is already explained clearly in the issue then this section is not needed. Explaining clearly why changes are proposed helps reviewers understand your changes and offer better suggestions for fixes. --> # What changes are included in this PR? <!-- There is no need to duplicate the description in the issue here but it is sometimes worth providing a summary of the individual changes in this PR. --> # Are these changes tested? <!-- We typically require tests for all PRs in order to: 1. Prevent the code from being accidentally broken by subsequent changes 2. Serve as another way to document the expected behavior of the code If tests are not included in your PR, please explain why (for example, are they covered by existing tests)? If this PR claims a performance improvement, please include evidence such as benchmark results. --> # Are there any user-facing changes? <!-- If there are user-facing changes then we may require documentation to be updated before approving the PR. If there are any breaking changes to public APIs, please call them out. -->
1 parent 6ba533d commit 7d9f620

2 files changed

Lines changed: 107 additions & 2 deletions

File tree

parquet/benches/arrow_reader.rs

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use parquet::{
3535
arrow::array_reader::ArrayReader,
3636
basic::Encoding,
3737
column::page::PageIterator,
38-
data_type::{ByteArrayType, Int32Type, Int64Type},
38+
data_type::{BoolType, ByteArrayType, Int32Type, Int64Type},
3939
schema::types::{ColumnDescPtr, SchemaDescPtr},
4040
};
4141
use rand::distr::uniform::SampleUniform;
@@ -105,6 +105,8 @@ fn build_test_schema() -> SchemaDescPtr {
105105
optional FIXED_LEN_BYTE_ARRAY(32) element;
106106
}
107107
}
108+
REQUIRED BOOLEAN mandatory_bool_leaf;
109+
OPTIONAL BOOLEAN optional_bool_leaf;
108110
}
109111
";
110112
parse_message_type(message_type)
@@ -337,6 +339,47 @@ where
337339
InMemoryPageIterator::new(pages)
338340
}
339341

342+
fn build_encoded_bool_page_iterator(
343+
column_desc: ColumnDescPtr,
344+
null_density: f32,
345+
encoding: Encoding,
346+
) -> impl PageIterator + Clone {
347+
let max_def_level = column_desc.max_def_level();
348+
let max_rep_level = column_desc.max_rep_level();
349+
let rep_levels = vec![0; VALUES_PER_PAGE];
350+
let mut rng = seedable_rng();
351+
let mut pages: Vec<Vec<parquet::column::page::Page>> = Vec::new();
352+
for _i in 0..NUM_ROW_GROUPS {
353+
let mut column_chunk_pages = Vec::new();
354+
for _j in 0..PAGES_PER_GROUP {
355+
// generate page
356+
let mut values = Vec::with_capacity(VALUES_PER_PAGE);
357+
let mut def_levels = Vec::with_capacity(VALUES_PER_PAGE);
358+
for _k in 0..VALUES_PER_PAGE {
359+
let def_level = if rng.random::<f32>() < null_density {
360+
max_def_level - 1
361+
} else {
362+
max_def_level
363+
};
364+
if def_level == max_def_level {
365+
let value = rng.random_bool(0.5);
366+
values.push(value);
367+
}
368+
def_levels.push(def_level);
369+
}
370+
let mut page_builder =
371+
DataPageBuilderImpl::new(column_desc.clone(), values.len() as u32, true);
372+
page_builder.add_rep_levels(max_rep_level, &rep_levels);
373+
page_builder.add_def_levels(max_def_level, &def_levels);
374+
page_builder.add_values::<BoolType>(encoding, &values);
375+
column_chunk_pages.push(page_builder.consume());
376+
}
377+
pages.push(column_chunk_pages);
378+
}
379+
380+
InMemoryPageIterator::new(pages)
381+
}
382+
340383
fn build_delta_encoded_incr_primitive_page_iterator<T>(
341384
column_desc: ColumnDescPtr,
342385
null_density: f32,
@@ -883,6 +926,16 @@ fn create_primitive_array_reader(
883926
.unwrap();
884927
Box::new(reader)
885928
}
929+
Type::BOOLEAN => {
930+
let reader = PrimitiveArrayReader::<BoolType>::new(
931+
Box::new(page_iterator),
932+
column_desc,
933+
None,
934+
DEFAULT_BATCH_SIZE,
935+
)
936+
.unwrap();
937+
Box::new(reader)
938+
}
886939
_ => unreachable!(),
887940
}
888941
}
@@ -1575,6 +1628,47 @@ fn bench_primitive<T>(
15751628
});
15761629
}
15771630

1631+
fn bench_boolean(
1632+
group: &mut BenchmarkGroup<WallTime>,
1633+
mandatory_column_desc: &ColumnDescPtr,
1634+
optional_column_desc: &ColumnDescPtr,
1635+
) {
1636+
let mut count: usize = 0;
1637+
1638+
// plain encoded, no NULLs
1639+
let data =
1640+
build_encoded_bool_page_iterator(mandatory_column_desc.clone(), 0.0, Encoding::PLAIN);
1641+
group.bench_function("plain encoded, mandatory, no NULLs", |b| {
1642+
b.iter(|| {
1643+
let array_reader =
1644+
create_primitive_array_reader(data.clone(), mandatory_column_desc.clone());
1645+
count = bench_array_reader(array_reader);
1646+
});
1647+
assert_eq!(count, EXPECTED_VALUE_COUNT);
1648+
});
1649+
1650+
let data = build_encoded_bool_page_iterator(optional_column_desc.clone(), 0.0, Encoding::PLAIN);
1651+
group.bench_function("plain encoded, optional, no NULLs", |b| {
1652+
b.iter(|| {
1653+
let array_reader =
1654+
create_primitive_array_reader(data.clone(), optional_column_desc.clone());
1655+
count = bench_array_reader(array_reader);
1656+
});
1657+
assert_eq!(count, EXPECTED_VALUE_COUNT);
1658+
});
1659+
1660+
// plain encoded, half NULLs
1661+
let data = build_encoded_bool_page_iterator(optional_column_desc.clone(), 0.5, Encoding::PLAIN);
1662+
group.bench_function("plain encoded, optional, half NULLs", |b| {
1663+
b.iter(|| {
1664+
let array_reader =
1665+
create_primitive_array_reader(data.clone(), optional_column_desc.clone());
1666+
count = bench_array_reader(array_reader);
1667+
});
1668+
assert_eq!(count, EXPECTED_VALUE_COUNT);
1669+
});
1670+
}
1671+
15781672
// Benchmark reading a struct with a single primitive field.
15791673
// No need to bench all encodings for the data, as that should already be covered by `bench_primitive`.
15801674
// The only performance difference should be caused by the additional definition level.
@@ -1801,6 +1895,8 @@ fn add_benches(c: &mut Criterion) {
18011895
let optional_struct_optional_in32_column_desc = schema.column(40);
18021896
let int32_list_desc = schema.column(41);
18031897
let fixed32_list_desc = schema.column(42);
1898+
let mandatory_bool_column_desc = schema.column(43);
1899+
let optional_bool_column_desc = schema.column(44);
18041900

18051901
// primitive / int32 benchmarks
18061902
// =============================
@@ -1894,6 +1990,15 @@ fn add_benches(c: &mut Criterion) {
18941990
);
18951991
group.finish();
18961992

1993+
// boolean benchmarks
1994+
let mut group = c.benchmark_group("arrow_array_reader/BooleanArray");
1995+
bench_boolean(
1996+
&mut group,
1997+
&mandatory_bool_column_desc,
1998+
&optional_bool_column_desc,
1999+
);
2000+
group.finish();
2001+
18972002
let mut group = c.benchmark_group("arrow_array_reader/struct/Int32Array");
18982003
bench_struct_primitive::<Int32Type>(
18992004
&mut group,

parquet/src/arrow/array_reader/primitive_array.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ native_buffer!(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64);
5656

5757
impl IntoBuffer for Vec<bool> {
5858
fn into_buffer(self, _target_type: &ArrowType) -> Buffer {
59-
BooleanBuffer::from_iter(self).into_inner()
59+
BooleanBuffer::from(self.as_slice()).into_inner()
6060
}
6161
}
6262

0 commit comments

Comments
 (0)