Skip to content

Commit 7ce5324

Browse files
committed
GH-55399: [C++][Parquet] Use CountIf16 for level counting in reader and writer
### Rationale for this change Follow-up to the `CountIf16` helper: use it for the level counting loops in the Parquet column reader/writer and in the file scanner, replacing open-coded scalar loops and `std::count` calls. Converted sites: - `TypedColumnReaderImpl::ReadLevelsInCurrentPage`: count non-null values. - `TypedRecordReader::SkipRecordsInBufferNonRepeated`: count values to skip. - `TypedRecordReader::DelimitRecords`: count records (rep level == 0) and values seen (def level == max). - `TypedRecordReader::ReadDenseForOptionalInBuffer`: count values to read. - `TypedColumnWriterImpl::WriteLevels`: count values to write (both overloads) and records written (rep level == 0). - `TypedColumnWriterImpl::MaybeCalculateValidityBits`: count values to write and spaced values to write. - `ScanFileContents`: count rows (rep level == 0). Sites are left as is where counting is not the bottleneck: the per-element bitmap loops in `DefRepLevelsToListInfo` also build validity bitmaps and write list offsets for every level, `page_index.cc` counts pages rather than levels, and the non-nullable validation loop in `WriteArrow` needs to `break` early on the first mismatch. `UpdateLevelHistogram`'s `max_level == 1` fast path is also left as is: its levels are known to be 0 or 1, so it sums them with `std::accumulate` in the `int16_t` space, which is a single 16-bit add per vector, while counting the 1s costs a compare and a subtract. ### What changes are included in this PR? Use `CountIf16` for the level counting call sites above. ### Are these changes tested? Covered by the existing Parquet reader, writer and Arrow reader/writer tests. ### Are there any user-facing changes? No.
1 parent fcb9cb5 commit 7ce5324

4 files changed

Lines changed: 52 additions & 51 deletions

File tree

‎cpp/src/parquet/column_reader.cc‎

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include "parquet/encryption/encryption_internal.h"
4848
#include "parquet/encryption/internal_file_decryptor.h"
4949
#include "parquet/exception.h"
50+
#include "parquet/level_comparison.h"
5051
#include "parquet/level_conversion.h"
5152
#include "parquet/level_decoder_internal.h"
5253
#include "parquet/properties.h"
@@ -1546,8 +1547,10 @@ class TypedColumnReaderImpl
15461547
// TODO(wesm): this tallying of values-to-decode can be performed with better
15471548
// cache-efficiency if fused with the level decoding.
15481549
ARROW_DCHECK_LE(*num_def_levels, std::numeric_limits<int32_t>::max());
1549-
*non_null_values_to_read += static_cast<int32_t>(
1550-
std::count(def_levels, def_levels + *num_def_levels, this->max_def_level()));
1550+
const int16_t max_def_level = this->max_def_level();
1551+
*non_null_values_to_read += static_cast<int32_t>(internal::CountIf16(
1552+
def_levels, *num_def_levels,
1553+
[max_def_level](int16_t level) { return level == max_def_level; }));
15511554
} else {
15521555
// Required field, read all values
15531556
if (num_def_levels != nullptr) {
@@ -1961,9 +1964,10 @@ int64_t TypedRecordReader<DT, VS>::SkipRecordsInBufferNonRepeated(int64_t num_re
19611964
// We skipped the levels by incrementing 'levels_position_'. For values
19621965
// we do not have a buffer, so we need to read them and throw them away.
19631966
// First we need to figure out how many present/not-null values there are.
1964-
const auto values_to_read = static_cast<int32_t>(
1965-
std::count(def_levels() + levels_position_, def_levels() + remaining_levels_pos,
1966-
this->max_def_level()));
1967+
const int16_t max_def_level = this->max_def_level();
1968+
const auto values_to_read = static_cast<int32_t>(internal::CountIf16(
1969+
def_levels() + levels_position_, remaining_levels_pos - levels_position_,
1970+
[max_def_level](int16_t level) { return level == max_def_level; }));
19671971

19681972
// Now that we have figured out number of values to read, we do not need
19691973
// these levels anymore. We will remove these values from the buffer.
@@ -2128,9 +2132,8 @@ int64_t TypedRecordReader<DT, VS>::DelimitRecords(int64_t num_records,
21282132
const int64_t stride =
21292133
std::min(levels_written() - levels_position_, num_records - records_read);
21302134
const int64_t position_end = levels_position_ + stride;
2131-
for (int64_t i = levels_position_; i < position_end; ++i) {
2132-
records_read += rep_levels[i] == 0;
2133-
}
2135+
records_read += CountIf16(rep_levels + levels_position_, stride,
2136+
[](int16_t rep_level) { return rep_level == 0; });
21342137
levels_position_ = position_end;
21352138
if (records_read == num_records) {
21362139
// Check last rep_level reaches the boundary and
@@ -2144,8 +2147,10 @@ int64_t TypedRecordReader<DT, VS>::DelimitRecords(int64_t num_records,
21442147
}
21452148
}
21462149
// Scan definition levels to find number of physical values
2147-
*values_seen = std::count(def_levels + level, def_levels + levels_position_,
2148-
this->max_def_level());
2150+
const int16_t max_def_level = this->max_def_level();
2151+
*values_seen = CountIf16(
2152+
def_levels + level, levels_position_ - level,
2153+
[max_def_level](int16_t def_level) { return def_level == max_def_level; });
21492154
return records_read;
21502155
}
21512156

@@ -2234,8 +2239,10 @@ void TypedRecordReader<DT, VS>::ReadDenseForOptionalInBuffer(
22342239

22352240
// When reading dense we need to figure out number of values to read.
22362241
const int16_t* def_levels = this->def_levels();
2237-
*values_to_read += std::count(def_levels + start_levels_position,
2238-
def_levels + levels_position_, this->max_def_level());
2242+
const int16_t max_def_level = this->max_def_level();
2243+
*values_to_read += CountIf16(
2244+
def_levels + start_levels_position, levels_position_ - start_levels_position,
2245+
[max_def_level](int16_t def_level) { return def_level == max_def_level; });
22392246
// This is only reading in the current page so this fits in an int32.
22402247
value_sink_.ReadValuesDense(*this->current_decoder_.get(),
22412248
clamp_to<int32_t>(*values_to_read));

‎cpp/src/parquet/column_writer.cc‎

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include "parquet/encoding.h"
5656
#include "parquet/encryption/encryption_internal.h"
5757
#include "parquet/encryption/internal_file_encryptor.h"
58+
#include "parquet/level_comparison.h"
5859
#include "parquet/level_conversion.h"
5960
#include "parquet/metadata.h"
6061
#include "parquet/page_index.h"
@@ -1698,11 +1699,10 @@ class TypedColumnWriterImpl : public ColumnWriterImpl,
16981699
int64_t values_to_write = 0;
16991700
// If the field is required and non-repeated, there are no definition levels
17001701
if (descr_->max_definition_level() > 0) {
1701-
for (int64_t i = 0; i < num_levels; ++i) {
1702-
if (def_levels[i] == descr_->max_definition_level()) {
1703-
++values_to_write;
1704-
}
1705-
}
1702+
const int16_t max_def_level = descr_->max_definition_level();
1703+
values_to_write = internal::CountIf16(
1704+
def_levels, num_levels,
1705+
[max_def_level](int16_t def_level) { return def_level == max_def_level; });
17061706

17071707
WriteDefinitionLevels(num_levels, def_levels);
17081708
} else {
@@ -1714,12 +1714,10 @@ class TypedColumnWriterImpl : public ColumnWriterImpl,
17141714
if (descr_->max_repetition_level() > 0) {
17151715
// A row could include more than one value
17161716
// Count the occasions where we start a new row
1717-
for (int64_t i = 0; i < num_levels; ++i) {
1718-
if (rep_levels[i] == 0) {
1719-
rows_written_++;
1720-
num_buffered_rows_++;
1721-
}
1722-
}
1717+
const int64_t records_written = internal::CountIf16(
1718+
rep_levels, num_levels, [](int16_t rep_level) { return rep_level == 0; });
1719+
rows_written_ += records_written;
1720+
num_buffered_rows_ += records_written;
17231721

17241722
WriteRepetitionLevels(num_levels, rep_levels);
17251723
} else {
@@ -1749,11 +1747,16 @@ class TypedColumnWriterImpl : public ColumnWriterImpl,
17491747
*out_spaced_values_to_write = batch_size;
17501748
*null_count = 0;
17511749
} else {
1752-
for (int x = 0; x < batch_size; x++) {
1753-
*out_values_to_write += def_levels[x] == level_info_.def_level ? 1 : 0;
1754-
*out_spaced_values_to_write +=
1755-
def_levels[x] >= level_info_.repeated_ancestor_def_level ? 1 : 0;
1756-
}
1750+
const int16_t def_level = level_info_.def_level;
1751+
const int16_t repeated_ancestor_def_level =
1752+
level_info_.repeated_ancestor_def_level;
1753+
*out_values_to_write += internal::CountIf16(
1754+
def_levels, batch_size,
1755+
[def_level](int16_t level) { return level == def_level; });
1756+
*out_spaced_values_to_write += internal::CountIf16(
1757+
def_levels, batch_size, [repeated_ancestor_def_level](int16_t level) {
1758+
return level >= repeated_ancestor_def_level;
1759+
});
17571760
*null_count = batch_size - *out_values_to_write;
17581761
}
17591762
return;
@@ -1809,12 +1812,10 @@ class TypedColumnWriterImpl : public ColumnWriterImpl,
18091812
if (descr_->max_repetition_level() > 0) {
18101813
// A row could include more than one value
18111814
// Count the occasions where we start a new row
1812-
for (int64_t i = 0; i < num_levels; ++i) {
1813-
if (rep_levels[i] == 0) {
1814-
rows_written_++;
1815-
num_buffered_rows_++;
1816-
}
1817-
}
1815+
const int64_t records_written = internal::CountIf16(
1816+
rep_levels, num_levels, [](int16_t rep_level) { return rep_level == 0; });
1817+
rows_written_ += records_written;
1818+
num_buffered_rows_ += records_written;
18181819
WriteRepetitionLevels(num_levels, rep_levels);
18191820
} else {
18201821
// Each value is exactly one row

‎cpp/src/parquet/file_reader.cc‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "parquet/encryption/internal_file_decryptor.h"
4545
#include "parquet/exception.h"
4646
#include "parquet/file_writer.h"
47+
#include "parquet/level_comparison.h"
4748
#include "parquet/metadata.h"
4849
#include "parquet/page_index.h"
4950
#include "parquet/platform.h"
@@ -977,11 +978,9 @@ int64_t ScanFileContents(std::vector<int> columns, const int32_t column_batch_si
977978
ScanAllValues(column_batch_size, def_levels.data(), rep_levels.data(),
978979
values.data(), &values_read, col_reader.get());
979980
if (col_reader->descr()->max_repetition_level() > 0) {
980-
for (size_t i = 0; i < static_cast<size_t>(levels_read); i++) {
981-
if (rep_levels[i] == 0) {
982-
total_rows[col]++;
983-
}
984-
}
981+
total_rows[col] +=
982+
internal::CountIf16(rep_levels.data(), levels_read,
983+
[](int16_t rep_level) { return rep_level == 0; });
985984
} else {
986985
total_rows[col] += levels_read;
987986
}

‎cpp/src/parquet/size_statistics.cc‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919

2020
#include <algorithm>
2121
#include <array>
22-
#include <numeric>
2322
#include <ostream>
2423
#include <span>
2524
#include <string_view>
2625
#include <vector>
2726

2827
#include "arrow/util/logging_internal.h"
2928
#include "parquet/exception.h"
29+
#include "parquet/level_comparison.h"
3030
#include "parquet/schema.h"
3131

3232
namespace parquet {
@@ -160,17 +160,11 @@ void UpdateLevelHistogram(std::span<const int16_t> levels, std::span<int64_t> hi
160160

161161
if (max_level == 1) {
162162
// Specialize the common case for non-repeated non-nested columns.
163-
// Summing the levels gives us the number of 1s, and the number of 0s follows.
164-
// We do repeated sums in the int16_t space, which the compiler is likely
165-
// to vectorize efficiently.
166-
constexpr int64_t kChunkSize = 1 << 14; // to avoid int16_t overflows
167-
int64_t hist1 = 0;
168-
auto it = levels.begin();
169-
while (it != levels.end()) {
170-
const auto chunk_size = std::min<int64_t>(levels.end() - it, kChunkSize);
171-
hist1 += std::accumulate(it, it + chunk_size, int16_t{0});
172-
it += chunk_size;
173-
}
163+
// Counting the 1s directly gives us the number of 1s, and the number of 0s
164+
// follows. CountIf16 accumulates in the int16_t space in chunks, which the
165+
// compiler vectorizes efficiently and which cannot overflow.
166+
const int64_t hist1 = internal::CountIf16(levels.data(), num_levels,
167+
[](int16_t level) { return level == 1; });
174168
histogram[0] += num_levels - hist1;
175169
histogram[1] += hist1;
176170
return;

0 commit comments

Comments
 (0)