Skip to content

Commit 74acebb

Browse files
committed
GH-50703: [C++][Parquet] Reserve dictionary byte array data
1 parent 3deec0b commit 74acebb

3 files changed

Lines changed: 162 additions & 13 deletions

File tree

cpp/src/parquet/decoder.cc

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,8 +1024,7 @@ class DictDecoderImpl : public TypedDecoderImpl<Type>, public DictDecoder<Type>
10241024
// memory use in most cases
10251025
std::shared_ptr<ResizableBuffer> byte_array_offsets_;
10261026

1027-
// Reusable buffer for decoding dictionary indices to be appended to a
1028-
// BinaryDictionary32Builder
1027+
// Reusable buffer for decoding dictionary indices into Arrow builders.
10291028
std::shared_ptr<ResizableBuffer> indices_scratch_space_;
10301029

10311030
::arrow::util::RleBitPackedDecoder<int32_t> idx_decoder_;
@@ -1295,12 +1294,80 @@ class DictByteArrayDecoderImpl : public DictDecoderImpl<ByteArrayType> {
12951294
int64_t valid_bits_offset,
12961295
typename EncodingTraits<ByteArrayType>::Accumulator* out,
12971296
int* out_num_values) {
1297+
const auto* dict_values = dictionary_->data_as<ByteArray>();
1298+
const int values_to_decode = num_values - null_count;
1299+
1300+
switch (out->builder->type()->id()) {
1301+
case ::arrow::Type::BINARY:
1302+
case ::arrow::Type::STRING:
1303+
case ::arrow::Type::LARGE_BINARY:
1304+
case ::arrow::Type::LARGE_STRING: {
1305+
if (values_to_decode > 0) {
1306+
RETURN_NOT_OK(indices_scratch_space_->TypedResize<int32_t>(
1307+
values_to_decode, /*shrink_to_fit=*/false));
1308+
}
1309+
auto* decoded_indices = indices_scratch_space_->mutable_data_as<int32_t>();
1310+
const int num_indices = idx_decoder_.GetBatch(decoded_indices, values_to_decode);
1311+
if (ARROW_PREDICT_FALSE(num_indices != values_to_decode)) {
1312+
return Status::Invalid("Invalid number of indices: ", num_indices);
1313+
}
1314+
1315+
int64_t data_length = 0;
1316+
for (int i = 0; i < values_to_decode; ++i) {
1317+
const auto index = decoded_indices[i];
1318+
RETURN_NOT_OK(IndexInBounds(index));
1319+
if (ARROW_PREDICT_FALSE(AddWithOverflow(
1320+
data_length, static_cast<int64_t>(dict_values[index].len),
1321+
&data_length))) {
1322+
return Status::Invalid(
1323+
"excess expansion while decoding dictionary-encoded BYTE_ARRAY");
1324+
}
1325+
}
1326+
1327+
auto append_predecoded = [&](auto* helper) {
1328+
int values_decoded = 0;
1329+
int pos_indices = 0;
1330+
int64_t remaining_data_length = data_length;
1331+
1332+
RETURN_NOT_OK(VisitBitRuns(
1333+
valid_bits, valid_bits_offset, num_values,
1334+
[&](int64_t position, int64_t length, bool valid) {
1335+
if (valid) {
1336+
for (int64_t i = 0; i < length; ++i) {
1337+
const auto& val = dict_values[decoded_indices[pos_indices++]];
1338+
RETURN_NOT_OK(helper->AppendValue(
1339+
val.ptr, static_cast<int32_t>(val.len), remaining_data_length));
1340+
remaining_data_length -= val.len;
1341+
}
1342+
values_decoded += static_cast<int>(length);
1343+
} else {
1344+
for (int64_t i = 0; i < length; ++i) {
1345+
helper->UnsafeAppendNull();
1346+
}
1347+
}
1348+
return Status::OK();
1349+
}));
1350+
DCHECK_EQ(pos_indices, values_to_decode);
1351+
DCHECK_EQ(remaining_data_length, 0);
1352+
*out_num_values = values_decoded;
1353+
return Status::OK();
1354+
};
1355+
1356+
return DispatchArrowBinaryHelper<ByteArrayType>(out, num_values, data_length,
1357+
append_predecoded);
1358+
}
1359+
default:
1360+
// Binary-view builders don't benefit from reserving the dictionary values'
1361+
// total byte length, since short values are stored inline. Keep their
1362+
// existing bounded streaming decode path. Unsupported builder types are
1363+
// rejected by DispatchArrowBinaryHelper below before decoding any indices.
1364+
break;
1365+
}
1366+
12981367
constexpr int32_t kBufferSize = 1024;
12991368
int32_t indices[kBufferSize];
13001369

1301-
auto visit_binary_helper = [&](auto* helper) {
1302-
const auto* dict_values = dictionary_->data_as<ByteArray>();
1303-
const int values_to_decode = num_values - null_count;
1370+
auto append_streaming = [&](auto* helper) {
13041371
int values_decoded = 0;
13051372
int num_indices = 0;
13061373
int pos_indices = 0;
@@ -1309,7 +1376,7 @@ class DictByteArrayDecoderImpl : public DictDecoderImpl<ByteArrayType> {
13091376
if (valid) {
13101377
while (length > 0) {
13111378
if (num_indices == pos_indices) {
1312-
// Refill indices buffer
1379+
// Refill the bounded indices buffer for binary-view builders.
13131380
const auto max_batch_size =
13141381
std::min<int32_t>(kBufferSize, values_to_decode - values_decoded);
13151382
num_indices = idx_decoder_.GetBatch(indices, max_batch_size);
@@ -1341,11 +1408,9 @@ class DictByteArrayDecoderImpl : public DictDecoderImpl<ByteArrayType> {
13411408
*out_num_values = values_decoded;
13421409
return Status::OK();
13431410
};
1344-
// The `len_` in the ByteArrayDictDecoder is the total length of the
1345-
// RLE/Bit-pack encoded data size, so, we cannot use `len_` to reserve
1346-
// space for binary data.
1411+
13471412
return DispatchArrowBinaryHelper<ByteArrayType>(
1348-
out, num_values, /*estimated_data_length=*/{}, visit_binary_helper);
1413+
out, num_values, /*estimated_data_length=*/{}, append_streaming);
13491414
}
13501415

13511416
template <typename BuilderType>

cpp/src/parquet/encoding_benchmark.cc

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,14 +1300,15 @@ class BenchmarkDecodeArrowByteArray : public BenchmarkDecodeArrowBase<ByteArrayT
13001300
}
13011301

13021302
void InitDataInputs() final {
1303-
// Generate a random string dictionary without any nulls so that this dataset can
1304-
// be used for benchmarking the DecodeArrowNonNull API
1303+
// The default dataset has no nulls so that it can also be used for benchmarking
1304+
// the DecodeArrowNonNull API. DecodeArrowWithNullDenseBenchmark regenerates it
1305+
// with the requested null probability.
13051306
constexpr int repeat_factor = 8;
13061307
constexpr int64_t min_length = 2;
13071308
constexpr int64_t max_length = 10;
13081309
::arrow::random::RandomArrayGenerator rag(0);
13091310
input_array_ = rag.StringWithRepeats(num_values_, num_values_ / repeat_factor,
1310-
min_length, max_length, /*null_probability=*/0);
1311+
min_length, max_length, null_probability_);
13111312
valid_bits_ = input_array_->null_bitmap_data();
13121313
total_size_ = input_array_->data()->buffers[2]->size();
13131314

@@ -1318,7 +1319,23 @@ class BenchmarkDecodeArrowByteArray : public BenchmarkDecodeArrowBase<ByteArrayT
13181319
}
13191320
}
13201321

1322+
void DecodeArrowWithNullDenseBenchmark(benchmark::State& state) {
1323+
null_probability_ = static_cast<double>(state.range(1)) / 10000;
1324+
InitDataInputs();
1325+
DoEncodeArrow();
1326+
1327+
for (auto _ : state) {
1328+
auto decoder = InitializeDecoder();
1329+
auto acc = CreateAccumulator();
1330+
decoder->DecodeArrow(num_values_, static_cast<int>(input_array_->null_count()),
1331+
valid_bits_, 0, &acc);
1332+
}
1333+
state.SetBytesProcessed(state.iterations() * total_size_);
1334+
state.SetItemsProcessed(state.iterations() * num_values_);
1335+
}
1336+
13211337
protected:
1338+
double null_probability_{0.0};
13221339
std::vector<ByteArray> values_;
13231340
};
13241341

@@ -1478,6 +1495,14 @@ class BM_ArrowBinaryViewDict : public BM_ArrowBinaryDict {
14781495
}
14791496
};
14801497

1498+
static void ByteArrayWithNullCustomArguments(benchmark::internal::Benchmark* b) {
1499+
b->ArgsProduct({
1500+
benchmark::CreateRange(MIN_RANGE, MAX_RANGE, /*multi=*/4),
1501+
{1000, 5000},
1502+
})
1503+
->ArgNames({"num_values", "null_in_ten_thousand"});
1504+
}
1505+
14811506
BENCHMARK_DEFINE_F(BM_ArrowBinaryDict, EncodeArrow)
14821507
(benchmark::State& state) { EncodeArrowBenchmark(state); }
14831508
BENCHMARK_REGISTER_F(BM_ArrowBinaryDict, EncodeArrow)->Range(1 << 18, 1 << 20);
@@ -1507,6 +1532,11 @@ BENCHMARK_DEFINE_F(BM_ArrowBinaryDict, DecodeArrow_Dense)(benchmark::State& stat
15071532
}
15081533
BENCHMARK_REGISTER_F(BM_ArrowBinaryDict, DecodeArrow_Dense)->Range(MIN_RANGE, MAX_RANGE);
15091534

1535+
BENCHMARK_DEFINE_F(BM_ArrowBinaryDict, DecodeArrowWithNull_Dense)
1536+
(benchmark::State& state) { DecodeArrowWithNullDenseBenchmark(state); }
1537+
BENCHMARK_REGISTER_F(BM_ArrowBinaryDict, DecodeArrowWithNull_Dense)
1538+
->Apply(ByteArrayWithNullCustomArguments);
1539+
15101540
BENCHMARK_DEFINE_F(BM_ArrowBinaryDict, DecodeArrowNonNull_Dense)
15111541
(benchmark::State& state) { DecodeArrowNonNullDenseBenchmark(state); }
15121542
BENCHMARK_REGISTER_F(BM_ArrowBinaryDict, DecodeArrowNonNull_Dense)
@@ -1527,6 +1557,11 @@ BENCHMARK_DEFINE_F(BM_ArrowBinaryViewDict, DecodeArrow_Dense)(benchmark::State&
15271557
BENCHMARK_REGISTER_F(BM_ArrowBinaryViewDict, DecodeArrow_Dense)
15281558
->Range(MIN_RANGE, MAX_RANGE);
15291559

1560+
BENCHMARK_DEFINE_F(BM_ArrowBinaryViewDict, DecodeArrowWithNull_Dense)
1561+
(benchmark::State& state) { DecodeArrowWithNullDenseBenchmark(state); }
1562+
BENCHMARK_REGISTER_F(BM_ArrowBinaryViewDict, DecodeArrowWithNull_Dense)
1563+
->Apply(ByteArrayWithNullCustomArguments);
1564+
15301565
BENCHMARK_DEFINE_F(BM_ArrowBinaryViewDict, DecodeArrowNonNull_Dense)
15311566
(benchmark::State& state) { DecodeArrowNonNullDenseBenchmark(state); }
15321567
BENCHMARK_REGISTER_F(BM_ArrowBinaryViewDict, DecodeArrowNonNull_Dense)

cpp/src/parquet/encoding_test.cc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "arrow/util/bitmap_writer.h"
4242
#include "arrow/util/checked_cast.h"
4343
#include "arrow/util/endian.h"
44+
#include "arrow/util/rle_encoding_internal.h"
4445
#include "arrow/util/string.h"
4546
#include "parquet/encoding.h"
4647
#include "parquet/platform.h"
@@ -1265,6 +1266,54 @@ TEST(DictEncodingAdHoc, ArrowBinaryDirectPut) {
12651266
::arrow::AssertArraysEqual(*values, *result);
12661267
}
12671268

1269+
TEST(DictEncodingAdHoc, DenseDecodeRejectsInvalidOrTruncatedIndices) {
1270+
auto dictionary =
1271+
::arrow::ArrayFromJSON(::arrow::binary(), R"(["a", "bb", "ccc"])");
1272+
auto owned_encoder = MakeTypedEncoder<ByteArrayType>(
1273+
Encoding::PLAIN, /*use_dictionary=*/true);
1274+
auto* encoder = dynamic_cast<DictEncoder<ByteArrayType>*>(owned_encoder.get());
1275+
ASSERT_NE(encoder, nullptr);
1276+
ASSERT_NO_THROW(encoder->PutDictionary(*dictionary));
1277+
1278+
auto dictionary_buffer =
1279+
AllocateBuffer(default_memory_pool(), encoder->dict_encoded_size());
1280+
encoder->WriteDict(dictionary_buffer->mutable_data());
1281+
1282+
auto dictionary_decoder =
1283+
MakeTypedDecoder<ByteArrayType>(Encoding::PLAIN, /*descr=*/nullptr);
1284+
dictionary_decoder->SetData(encoder->num_entries(), dictionary_buffer->data(),
1285+
static_cast<int>(dictionary_buffer->size()));
1286+
1287+
auto decoder = MakeDictDecoder<ByteArrayType>();
1288+
decoder->SetDict(dictionary_decoder.get());
1289+
1290+
auto ExpectDecodeFailure = [&](const uint8_t* data, int size) {
1291+
decoder->SetData(/*num_values=*/1, data, size);
1292+
typename EncodingTraits<ByteArrayType>::Accumulator acc;
1293+
acc.builder = std::make_unique<::arrow::BinaryBuilder>();
1294+
ASSERT_THROW(
1295+
decoder->DecodeArrow(/*num_values=*/1, /*null_count=*/0,
1296+
/*valid_bits=*/nullptr, /*valid_bits_offset=*/0, &acc),
1297+
ParquetException);
1298+
};
1299+
1300+
constexpr int kBitWidth = 2;
1301+
std::vector<uint8_t> invalid_index_data(
1302+
1 + ::arrow::util::RleBitPackedEncoder::MaxBufferSize(
1303+
kBitWidth, /*num_values=*/1) +
1304+
::arrow::util::RleBitPackedEncoder::MinBufferSize(kBitWidth));
1305+
invalid_index_data[0] = kBitWidth;
1306+
::arrow::util::RleBitPackedEncoder index_encoder(
1307+
invalid_index_data.data() + 1,
1308+
static_cast<int>(invalid_index_data.size() - 1), kBitWidth);
1309+
ASSERT_TRUE(index_encoder.Put(/*value=*/3));
1310+
const int invalid_index_size = 1 + index_encoder.Flush();
1311+
ExpectDecodeFailure(invalid_index_data.data(), invalid_index_size);
1312+
1313+
const uint8_t truncated_index_data[] = {kBitWidth};
1314+
ExpectDecodeFailure(truncated_index_data, sizeof(truncated_index_data));
1315+
}
1316+
12681317
TEST(DictEncodingAdHoc, PutDictionaryPutIndices) {
12691318
// Part of ARROW-3246
12701319
auto dict_values =

0 commit comments

Comments
 (0)