Skip to content

Commit 5815dc0

Browse files
AntoinePrvCopilot
andauthored
GH-50421: [C++][Parquet] Add LevelDecoder Skip and Count (#50422)
### Rationale for this change Adding new methods to the `LevelDecoder` to start reducing the complexity in `ColumnReaderImplBase`, `TypedColumnReaderImpl`, and `TypedRecordReader`. ### What changes are included in this PR? - Simplify `LevelDecoder` members based on a single variant - Add `LevelDecoder::Skip` - Add `LevelDecoder::CountUpTo` - Simplify `Skip` implementation in `TypedColumnReaderImpl` - Add `SkippableTypedDecoder` to wrap `TypedDecoder` with the remaining usage of the `scratch_for_skip_` buffer ### Are these changes tested? Yes. ### Are there any user-facing changes? No. * GitHub Issue: #50421 Lead-authored-by: AntoinePrv <AntoinePrv@users.noreply.github.com> Co-authored-by: Antoine Prouvost <AntoinePrv@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Antoine Pitrou <antoine@python.org>
1 parent 86c81bf commit 5815dc0

4 files changed

Lines changed: 644 additions & 259 deletions

File tree

cpp/src/arrow/util/rle_encoding_internal.h

Lines changed: 211 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,18 @@ class RleBitPackedParser {
258258
std::pair<rle_size_t, ControlFlow> PeekImpl(Handler&&) const;
259259
};
260260

261+
template <typename T>
262+
struct RleCountUpToParams {
263+
T value;
264+
rle_size_t batch_size;
265+
rle_size_t value_bit_width;
266+
};
267+
268+
struct RleCountUpToResult {
269+
rle_size_t matching_count;
270+
rle_size_t processed_count;
271+
};
272+
261273
/// Decoder class for a single run of RLE encoded data.
262274
template <typename T>
263275
class RleRunDecoder {
@@ -300,6 +312,15 @@ class RleRunDecoder {
300312
return steps;
301313
}
302314

315+
/// Advance and count the number of occurrences of a value.
316+
///
317+
/// The count is limited to at most the next `batch_size` items.
318+
/// @return The matching value count and number of elements that were processed.
319+
RleCountUpToResult CountUpTo(const RleCountUpToParams<value_type>& p) {
320+
const auto steps = Advance(p.batch_size);
321+
return {.matching_count = steps * (p.value == value_), .processed_count = steps};
322+
}
323+
303324
/// Get the next value and return false if there are no more.
304325
[[nodiscard]] constexpr bool Get(value_type* out_value, rle_size_t value_bit_width) {
305326
return GetBatch(out_value, 1, value_bit_width) == 1;
@@ -363,6 +384,29 @@ class BitPackedRunDecoder {
363384
return steps;
364385
}
365386

387+
/// Advance and count the number of occurrence of a values.
388+
///
389+
/// The count is limited to at most the next `batch_size` items.
390+
/// @return The matching value count and number of of element that were processed.
391+
RleCountUpToResult CountUpTo(const RleCountUpToParams<value_type>& p) {
392+
// Decoding in a stack buffer of 512 values: 1KB for int16_t used in levels
393+
// and up to 4KB for uint64_t.
394+
constexpr rle_size_t kBufferValueCount = 512;
395+
alignas(16) value_type buffer[kBufferValueCount]; // uninitialized
396+
397+
rle_size_t remaining = p.batch_size;
398+
rle_size_t count = 0;
399+
rle_size_t read_iter = 0;
400+
do {
401+
const auto batch_iter = std::min(remaining, kBufferValueCount);
402+
read_iter = GetBatch(buffer, batch_iter, p.value_bit_width);
403+
count += static_cast<rle_size_t>(std::count(buffer, buffer + read_iter, p.value));
404+
remaining -= read_iter;
405+
} while (remaining > 0 && read_iter > 0);
406+
407+
return {.matching_count = count, .processed_count = p.batch_size - remaining};
408+
}
409+
366410
/// Get the next value and return false if there are no more.
367411
[[nodiscard]] constexpr bool Get(value_type* out_value, rle_size_t value_bit_width) {
368412
return GetBatch(out_value, 1, value_bit_width) == 1;
@@ -448,6 +492,16 @@ class RleBitPackedDecoder {
448492
/// This is how one can check for errors.
449493
bool exhausted() const { return (run_remaining() == 0) && parser_.exhausted(); }
450494

495+
/// Advance by as many values as provided or until exhaustion of the decoder.
496+
/// Return the number of values skipped.
497+
[[nodiscard]] rle_size_t Advance(rle_size_t batch_size);
498+
499+
/// Advance and count the number of occurrence of a values.
500+
///
501+
/// The count is limited to at most the next `batch_size` items.
502+
/// @return The matching value count and number of of element that were processed.
503+
RleCountUpToResult CountUpTo(value_type value, rle_size_t batch_size);
504+
451505
/// Gets the next value or returns false if there are no more or an error occurred.
452506
///
453507
/// NB: Because the encoding only supports literal runs with lengths
@@ -500,12 +554,15 @@ class RleBitPackedDecoder {
500554
return std::visit([](const auto& dec) { return dec.remaining(); }, decoder_);
501555
}
502556

503-
/// Get a batch of values from the current run and return the number elements read.
504-
[[nodiscard]] rle_size_t RunGetBatch(value_type* out, rle_size_t batch_size) {
505-
return std::visit(
506-
[&](auto& dec) { return dec.GetBatch(out, batch_size, value_bit_width_); },
507-
decoder_);
508-
}
557+
/// Process data in the current run and subsequent ones.
558+
///
559+
/// `func` is called as `func(decoder, run_batch_size)` where `decoder` are
560+
/// statically-typed run decoder (not the variant).
561+
/// Must return the number of values it processed.
562+
///
563+
/// Return the number of values processed.
564+
template <typename Callable>
565+
rle_size_t ProcessValues(Callable&& func, rle_size_t batch_size);
509566

510567
/// Utility methods for retrieving spaced values.
511568
template <typename Converter>
@@ -515,6 +572,82 @@ class RleBitPackedDecoder {
515572
int64_t valid_bits_offset, rle_size_t null_count);
516573
};
517574

575+
/// Minimal decoder for legacy bit packed encoding (BIT_PACKED = 4).
576+
///
577+
/// The number of values that the decoder can represent is up to 2^31 - 1.
578+
template <typename T>
579+
class BitPackedDecoder : private BitPackedRunDecoder<T> {
580+
private:
581+
using Base = BitPackedRunDecoder<T>;
582+
583+
public:
584+
/// The type in which the data should be decoded.
585+
using value_type = T;
586+
587+
using Base::Advance;
588+
589+
BitPackedDecoder() noexcept = default;
590+
591+
/// Create a decoder object.
592+
///
593+
/// Unless explicitly given, then number of values is deduced from the data size,
594+
/// in which case it may read more values from the input stream than the user
595+
/// intended (to reach final byte alignment). This option is mandatory if the
596+
/// `value_bit_width` is zero.
597+
///
598+
/// @param data and data_size are the raw bytes to decode.
599+
/// @param value_bit_width is the size in bits of each encoded value.
600+
/// @param value_count is an optional number of values in the run.
601+
BitPackedDecoder(const uint8_t* data, rle_size_t data_size, rle_size_t value_bit_width,
602+
rle_size_t value_count) noexcept {
603+
Reset(data, data_size, value_bit_width, value_count);
604+
}
605+
606+
void Reset(const uint8_t* data, rle_size_t data_size, rle_size_t value_bit_width,
607+
rle_size_t value_count) noexcept {
608+
ARROW_DCHECK_GE(value_count, 0);
609+
ARROW_DCHECK_GE(value_bit_width, 0);
610+
ARROW_DCHECK_LE(value_bit_width, 64);
611+
612+
value_bit_width_ = value_bit_width;
613+
614+
ARROW_DCHECK_LE(value_count, std::numeric_limits<rle_size_t>::max());
615+
const auto run = BitPackedRun{
616+
/* data= */ data,
617+
/* value_count= */ value_count,
618+
/* value_bit_width= */ value_bit_width,
619+
/* max_read_bytes= */ data_size,
620+
};
621+
return Base::Reset(run, value_bit_width);
622+
}
623+
624+
/// Whether there is still values to iterate over.
625+
bool exhausted() const { return Base::remaining() == 0; }
626+
627+
/// Advance and count the number of occurrence of a values.
628+
///
629+
/// The count is limited to at most the next `batch_size` items.
630+
/// @return The matching value count and number of of element that were processed.
631+
RleCountUpToResult CountUpTo(value_type value, rle_size_t batch_size) {
632+
return Base::CountUpTo({
633+
.value = value,
634+
.batch_size = batch_size,
635+
.value_bit_width = value_bit_width_,
636+
});
637+
}
638+
639+
/// Gets the next value or returns false if there are no more or an error occurred.
640+
[[nodiscard]] bool Get(value_type* val) { return Base::Get(val, value_bit_width_); }
641+
642+
/// Get a batch of values return the number of decoded elements.
643+
[[nodiscard]] rle_size_t GetBatch(value_type* out, rle_size_t batch_size) {
644+
return Base::GetBatch(out, batch_size, value_bit_width_);
645+
}
646+
647+
private:
648+
rle_size_t value_bit_width_ = {};
649+
};
650+
518651
/// Class to incrementally build the rle data. This class does not allocate any memory.
519652
/// The encoding has two modes: encoding repeated runs and literal runs.
520653
/// If the run is sufficiently short, it is more efficient to encode as a literal run.
@@ -782,30 +915,26 @@ struct RleBitPackedDecoderGetRunDecoder<T, BitPackedRun> {
782915
};
783916

784917
template <typename T>
785-
bool RleBitPackedDecoder<T>::Get(value_type* val) {
786-
return GetBatch(val, 1) == 1;
787-
}
788-
789-
template <typename T>
790-
auto RleBitPackedDecoder<T>::GetBatch(value_type* out,
791-
rle_size_t batch_size) -> rle_size_t {
918+
template <typename Callable>
919+
auto RleBitPackedDecoder<T>::ProcessValues(Callable&& func,
920+
rle_size_t batch_size) -> rle_size_t {
792921
using ControlFlow = RleBitPackedParser::ControlFlow;
793922

794923
if (ARROW_PREDICT_FALSE(batch_size == 0 || exhausted())) {
795924
return 0;
796925
}
797926

798-
rle_size_t values_read = 0;
927+
rle_size_t values_processed = 0;
799928

800929
// Remaining from a previous call that would have left some unread data from a run.
801930
if (ARROW_PREDICT_FALSE(run_remaining() > 0)) {
802-
const auto read = RunGetBatch(out, batch_size);
803-
values_read += read;
804-
out += read;
931+
const auto processed =
932+
std::visit([&](auto& decoder) { return func(decoder, batch_size); }, decoder_);
933+
values_processed += processed;
805934

806935
// Either we fulfilled all the batch to be read or we finished remaining run.
807-
if (ARROW_PREDICT_FALSE(values_read == batch_size)) {
808-
return values_read;
936+
if (ARROW_PREDICT_FALSE(values_processed == batch_size)) {
937+
return values_processed;
809938
}
810939
ARROW_DCHECK(run_remaining() == 0);
811940
}
@@ -814,23 +943,66 @@ auto RleBitPackedDecoder<T>::GetBatch(value_type* out,
814943
using RunDecoder =
815944
typename RleBitPackedDecoderGetRunDecoder<value_type, decltype(run)>::type;
816945

817-
ARROW_DCHECK_LT(values_read, batch_size);
946+
ARROW_DCHECK_LT(values_processed, batch_size);
947+
// Local decoder to keep its type transparent to the compiler.
818948
RunDecoder decoder(run, value_bit_width_);
819-
const auto read = decoder.GetBatch(out, batch_size - values_read, value_bit_width_);
820-
ARROW_DCHECK_LE(read, batch_size - values_read);
821-
values_read += read;
822-
out += read;
949+
const auto read = func(decoder, batch_size - values_processed);
950+
ARROW_DCHECK_LE(read, batch_size - values_processed);
951+
values_processed += read;
823952

824-
// Stop reading and store remaining decoder
825-
if (ARROW_PREDICT_FALSE(values_read == batch_size || read == 0)) {
953+
if (ARROW_PREDICT_FALSE(values_processed == batch_size || read == 0)) {
954+
// Stop reading and store remaining decoder
826955
decoder_ = std::move(decoder);
827956
return ControlFlow::Break;
828957
}
829-
830958
return ControlFlow::Continue;
831959
});
832960

833-
return values_read;
961+
return values_processed;
962+
}
963+
964+
template <typename T>
965+
auto RleBitPackedDecoder<T>::Advance(rle_size_t batch_size) -> rle_size_t {
966+
return ProcessValues(
967+
[](auto& decoder, rle_size_t run_batch_size) {
968+
return decoder.Advance(run_batch_size);
969+
},
970+
batch_size);
971+
}
972+
973+
template <typename T>
974+
RleCountUpToResult RleBitPackedDecoder<T>::CountUpTo(value_type value,
975+
rle_size_t batch_size) {
976+
rle_size_t count = 0;
977+
const rle_size_t processed_count = ProcessValues(
978+
[value, this, &count](auto& decoder, rle_size_t run_batch_size) {
979+
const auto result = decoder.CountUpTo({
980+
.value = value,
981+
.batch_size = run_batch_size,
982+
.value_bit_width = value_bit_width_,
983+
});
984+
count += result.matching_count;
985+
return result.processed_count;
986+
},
987+
batch_size);
988+
return {.matching_count = count, .processed_count = processed_count};
989+
}
990+
991+
template <typename T>
992+
bool RleBitPackedDecoder<T>::Get(value_type* val) {
993+
return GetBatch(val, 1) == 1;
994+
}
995+
996+
template <typename T>
997+
auto RleBitPackedDecoder<T>::GetBatch(value_type* out,
998+
rle_size_t batch_size) -> rle_size_t {
999+
return ProcessValues(
1000+
[&out, this](auto& decoder, rle_size_t run_batch_size) {
1001+
const auto read = decoder.GetBatch(out, run_batch_size, value_bit_width_);
1002+
out += read;
1003+
return read;
1004+
},
1005+
batch_size);
8341006
}
8351007

8361008
namespace internal {
@@ -1269,68 +1441,27 @@ template <typename V>
12691441
auto RleBitPackedDecoder<T>::GetBatchWithDict(const V* dictionary,
12701442
int32_t dictionary_length, V* out,
12711443
rle_size_t batch_size) -> rle_size_t {
1272-
using ControlFlow = RleBitPackedParser::ControlFlow;
1273-
12741444
if (ARROW_PREDICT_FALSE(batch_size <= 0 || dictionary_length == 0)) {
12751445
// Either empty batch or invalid dictionary
12761446
return 0;
12771447
}
12781448

12791449
internal::DictionaryConverter<V, value_type> converter{dictionary, dictionary_length};
12801450

1281-
// Make lightweight BitRun class to reuse previous methods.
1451+
// Make lightweight BitRun class to reuse the spaced code path with no nulls.
12821452
constexpr internal::UnreachableBitRunReader validity_reader{};
12831453
internal::AllSetBitRun validity_run = {batch_size};
12841454

1285-
rle_size_t values_read = 0;
1286-
auto batch_values_remaining = [&]() {
1287-
ARROW_DCHECK_LE(values_read, batch_size);
1288-
return batch_size - values_read;
1289-
};
1290-
1291-
if (ARROW_PREDICT_FALSE(run_remaining() > 0)) {
1292-
const auto read = internal::RunGetSpaced(&converter, out, batch_size,
1293-
/* null_count= */ 0, value_bit_width_,
1294-
&validity_reader, &validity_run, &decoder_);
1295-
1296-
ARROW_DCHECK_EQ(read.null_read, 0);
1297-
values_read += read.values_read;
1298-
out += read.values_read;
1299-
1300-
// Either we fulfilled all the batch values to be read
1301-
if (ARROW_PREDICT_FALSE(values_read >= batch_size)) {
1302-
// There may be remaining null if they are not greedily filled
1303-
return values_read;
1304-
}
1305-
1306-
// We finished the remaining run
1307-
ARROW_DCHECK(run_remaining() == 0);
1308-
}
1309-
1310-
parser_.ParseWithCallable([&](auto run) {
1311-
using RunDecoder =
1312-
typename RleBitPackedDecoderGetRunDecoder<value_type, decltype(run)>::type;
1313-
1314-
RunDecoder decoder(run, value_bit_width_);
1315-
1316-
const auto read = internal::RunGetSpaced(&converter, out, batch_values_remaining(),
1317-
/* null_count= */ 0, value_bit_width_,
1318-
&validity_reader, &validity_run, &decoder);
1319-
1320-
ARROW_DCHECK_EQ(read.null_read, 0);
1321-
values_read += read.values_read;
1322-
out += read.values_read;
1323-
1324-
// Stop reading and store remaining decoder
1325-
if (ARROW_PREDICT_FALSE(read.values_read == 0 || values_read == batch_size)) {
1326-
decoder_ = std::move(decoder);
1327-
return ControlFlow::Break;
1328-
}
1329-
1330-
return ControlFlow::Continue;
1331-
});
1332-
1333-
return values_read;
1455+
return ProcessValues(
1456+
[&](auto& decoder, rle_size_t run_batch_size) {
1457+
const auto read = internal::RunGetSpaced(
1458+
&converter, out, run_batch_size, /* null_count= */ 0, value_bit_width_,
1459+
&validity_reader, &validity_run, &decoder);
1460+
ARROW_DCHECK_EQ(read.null_read, 0);
1461+
out += read.values_read;
1462+
return read.values_read;
1463+
},
1464+
batch_size);
13341465
}
13351466

13361467
template <typename T>

0 commit comments

Comments
 (0)