Skip to content

Commit a0bfd9d

Browse files
Eric Danielwesm
authored andcommitted
PARQUET-671: performance improvements for rle/bit-packed decoding
Testing on my own data shows an order-of-magnitude improvement. I separated the commits for clarity, each one gives an imcremental improvement. The motivation for the last commit (allowing NULL for def_levels/rep_level) is a workaround for Spark which doesn't seem to be able to generate columns without def_level, even when a column is specified as "not nullable". Author: Eric Daniel <edaniel@comscore.com> Closes apache#140 from edani/decode-perf and squashes the following commits: eec0855 [Eric Daniel] Ran "make format" 0568de6 [Eric Daniel] Only check num. of repetition levels when def_levels is set 5f54e1c [Eric Daniel] Added benchmarks for dictionary decoding 087945b [Eric Daniel] Style fixes from code review 906be73 [Eric Daniel] Allow the reader to skip rep/def decoding 04b7391 [Eric Daniel] Fast bit unpacking bda5d84 [Eric Daniel] The bit reader can decode in batches 3f10378 [Eric Daniel] Improve decoding of repeated values in the dict encoding Change-Id: I45421fc2ada5d06863ddd765470f79b45ec4991a
1 parent b283264 commit a0bfd9d

10 files changed

Lines changed: 3549 additions & 67 deletions

File tree

cpp/src/parquet/column/levels.cc

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,7 @@ int LevelDecoder::Decode(int batch_size, int16_t* levels) {
133133
if (encoding_ == Encoding::RLE) {
134134
num_decoded = rle_decoder_->GetBatch(levels, num_values);
135135
} else {
136-
for (int i = 0; i < num_values; ++i) {
137-
if (!bit_packed_decoder_->GetValue(bit_width_, levels + i)) { break; }
138-
++num_decoded;
139-
}
136+
num_decoded = bit_packed_decoder_->GetBatch(bit_width_, levels, num_values);
140137
}
141138
num_values_remaining_ -= num_decoded;
142139
return num_decoded;

cpp/src/parquet/column/reader.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ class PARQUET_EXPORT TypedColumnReader : public ColumnReader {
115115
// may be less than the number of repetition and definition levels. With
116116
// nested data this is almost certainly true.
117117
//
118+
// Set def_levels or rep_levels to nullptr if you want to skip reading them.
119+
// This is only safe if you know through some other source that there are no
120+
// undefined values.
121+
//
118122
// To fully exhaust a row group, you must read batches until the number of
119123
// values read reaches the number of stored values according to the metadata.
120124
//
@@ -171,7 +175,7 @@ inline int64_t TypedColumnReader<DType>::ReadBatch(int batch_size, int16_t* def_
171175
int64_t values_to_read = 0;
172176

173177
// If the field is required and non-repeated, there are no definition levels
174-
if (descr_->max_definition_level() > 0) {
178+
if (descr_->max_definition_level() > 0 && def_levels) {
175179
num_def_levels = ReadDefinitionLevels(batch_size, def_levels);
176180
// TODO(wesm): this tallying of values-to-decode can be performed with better
177181
// cache-efficiency if fused with the level decoding.
@@ -184,9 +188,9 @@ inline int64_t TypedColumnReader<DType>::ReadBatch(int batch_size, int16_t* def_
184188
}
185189

186190
// Not present for non-repeated fields
187-
if (descr_->max_repetition_level() > 0) {
191+
if (descr_->max_repetition_level() > 0 && rep_levels) {
188192
num_rep_levels = ReadRepetitionLevels(batch_size, rep_levels);
189-
if (num_def_levels != num_rep_levels) {
193+
if (def_levels && num_def_levels != num_rep_levels) {
190194
throw ParquetException("Number of decoded rep / def levels did not match");
191195
}
192196
}

cpp/src/parquet/encodings/dictionary-encoding.h

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,15 @@ class DictionaryDecoder : public Decoder<Type> {
6464

6565
virtual int Decode(T* buffer, int max_values) {
6666
max_values = std::min(max_values, num_values_);
67-
for (int i = 0; i < max_values; ++i) {
68-
buffer[i] = dictionary_[index()];
69-
}
67+
int decoded_values = idx_decoder_.GetBatchWithDict(dictionary_, buffer, max_values);
68+
if (decoded_values != max_values) { ParquetException::EofException(); }
69+
num_values_ -= max_values;
7070
return max_values;
7171
}
7272

7373
private:
7474
using Decoder<Type>::num_values_;
7575

76-
int index() {
77-
int idx = 0;
78-
if (!idx_decoder_.Get(&idx)) ParquetException::EofException();
79-
--num_values_;
80-
return idx;
81-
}
82-
8376
// Only one is set.
8477
Vector<T> dictionary_;
8578

@@ -177,7 +170,12 @@ class DictEncoderBase {
177170
/// Returns a conservative estimate of the number of bytes needed to encode the buffered
178171
/// indices. Used to size the buffer passed to WriteIndices().
179172
int EstimatedDataEncodedSize() {
180-
return 1 + RleEncoder::MaxBufferSize(bit_width(), buffered_indices_.size());
173+
// Note: because of the way RleEncoder::CheckBufferFull() is called, we have to
174+
// reserve
175+
// an extra "RleEncoder::MinBufferSize" bytes. These extra bytes won't be used
176+
// but not reserving them would cause the encoder to fail.
177+
return 1 + RleEncoder::MaxBufferSize(bit_width(), buffered_indices_.size()) +
178+
RleEncoder::MinBufferSize(bit_width());
181179
}
182180

183181
/// The minimum bit width required to encode the currently buffered indices.

cpp/src/parquet/encodings/encoding-benchmark.cc

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,23 @@
1717

1818
#include "benchmark/benchmark.h"
1919

20-
#include "parquet/encodings/plain-encoding.h"
20+
#include "parquet/encodings/dictionary-encoding.h"
21+
#include "parquet/file/reader-internal.h"
22+
#include "parquet/util/mem-pool.h"
2123

2224
namespace parquet {
2325

26+
using format::ColumnChunk;
27+
using schema::PrimitiveNode;
28+
2429
namespace benchmark {
2530

31+
std::shared_ptr<ColumnDescriptor> Int64Schema(Repetition::type repetition) {
32+
auto node = PrimitiveNode::Make("int64", repetition, Type::INT64);
33+
return std::make_shared<ColumnDescriptor>(
34+
node, repetition != Repetition::REQUIRED, repetition == Repetition::REPEATED);
35+
}
36+
2637
static void BM_PlainEncodingBoolean(::benchmark::State& state) {
2738
std::vector<bool> values(state.range_x(), 64);
2839
PlainEncoder<BooleanType> encoder(nullptr);
@@ -86,6 +97,65 @@ static void BM_PlainDecodingInt64(::benchmark::State& state) {
8697

8798
BENCHMARK(BM_PlainDecodingInt64)->Range(1024, 65536);
8899

100+
template <typename Type>
101+
static void DecodeDict(
102+
std::vector<typename Type::c_type>& values, ::benchmark::State& state) {
103+
typedef typename Type::c_type T;
104+
int num_values = values.size();
105+
106+
MemPool pool;
107+
MemoryAllocator* allocator = default_allocator();
108+
std::shared_ptr<ColumnDescriptor> descr = Int64Schema(Repetition::REQUIRED);
109+
std::shared_ptr<OwnedMutableBuffer> dict_buffer =
110+
std::make_shared<OwnedMutableBuffer>();
111+
auto indices = std::make_shared<OwnedMutableBuffer>();
112+
113+
DictEncoder<T> encoder(&pool, allocator, descr->type_length());
114+
for (int i = 0; i < num_values; ++i) {
115+
encoder.Put(values[i]);
116+
}
117+
118+
dict_buffer->Resize(encoder.dict_encoded_size());
119+
encoder.WriteDict(dict_buffer->mutable_data());
120+
indices->Resize(encoder.EstimatedDataEncodedSize());
121+
int actual_bytes = encoder.WriteIndices(indices->mutable_data(), indices->size());
122+
indices->Resize(actual_bytes);
123+
124+
while (state.KeepRunning()) {
125+
PlainDecoder<Type> dict_decoder(descr.get());
126+
dict_decoder.SetData(encoder.num_entries(), dict_buffer->data(), dict_buffer->size());
127+
DictionaryDecoder<Type> decoder(descr.get());
128+
decoder.SetDict(&dict_decoder);
129+
decoder.SetData(num_values, indices->data(), indices->size());
130+
decoder.Decode(values.data(), num_values);
131+
}
132+
133+
state.SetBytesProcessed(state.iterations() * state.range_x() * sizeof(T));
134+
}
135+
136+
static void BM_DictDecodingInt64_repeats(::benchmark::State& state) {
137+
typedef Int64Type Type;
138+
typedef typename Type::c_type T;
139+
140+
std::vector<T> values(state.range_x(), 64);
141+
DecodeDict<Type>(values, state);
142+
}
143+
144+
BENCHMARK(BM_DictDecodingInt64_repeats)->Range(1024, 65536);
145+
146+
static void BM_DictDecodingInt64_literals(::benchmark::State& state) {
147+
typedef Int64Type Type;
148+
typedef typename Type::c_type T;
149+
150+
std::vector<T> values(state.range_x());
151+
for (size_t i = 0; i < values.size(); ++i) {
152+
values[i] = i;
153+
}
154+
DecodeDict<Type>(values, state);
155+
}
156+
157+
BENCHMARK(BM_DictDecodingInt64_literals)->Range(1024, 65536);
158+
89159
} // namespace benchmark
90160

91161
} // namespace parquet

cpp/src/parquet/encodings/plain-encoding.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,8 @@ class PlainDecoder<BooleanType> : public Decoder<BooleanType> {
142142

143143
virtual int Decode(bool* buffer, int max_values) {
144144
max_values = std::min(max_values, num_values_);
145-
bool val;
146-
for (int i = 0; i < max_values; ++i) {
147-
if (!bit_reader_.GetValue(1, &val)) { ParquetException::EofException(); }
148-
buffer[i] = val;
145+
if (bit_reader_.GetBatch(1, buffer, max_values) != max_values) {
146+
ParquetException::EofException();
149147
}
150148
num_values_ -= max_values;
151149
return max_values;

cpp/src/parquet/util/bit-stream-utils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ class BitReader {
122122
template <typename T>
123123
bool GetValue(int num_bits, T* v);
124124

125+
/// Get a number of values from the buffer. Return the number of values actually read.
126+
template <typename T>
127+
int GetBatch(int num_bits, T* v, int batch_size);
128+
125129
/// Reads a 'num_bytes'-sized value from the buffer and stores it in 'v'. T
126130
/// needs to be a little-endian native type and big enough to store
127131
/// 'num_bytes'. The value is assumed to be byte-aligned so the stream will

cpp/src/parquet/util/bit-stream-utils.inline.h

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
#ifndef PARQUET_UTIL_BIT_STREAM_UTILS_INLINE_H
2121
#define PARQUET_UTIL_BIT_STREAM_UTILS_INLINE_H
2222

23+
#include <algorithm>
24+
2325
#include "parquet/util/bit-stream-utils.h"
26+
#include "parquet/util/bpacking.h"
2427

2528
namespace parquet {
2629

@@ -85,35 +88,98 @@ inline bool BitWriter::PutVlqInt(uint32_t v) {
8588
return result;
8689
}
8790

91+
template <typename T>
92+
inline void GetValue_(int num_bits, T* v, int max_bytes, const uint8_t* buffer,
93+
int* bit_offset, int* byte_offset, uint64_t* buffered_values) {
94+
*v = BitUtil::TrailingBits(*buffered_values, *bit_offset + num_bits) >> *bit_offset;
95+
96+
*bit_offset += num_bits;
97+
if (*bit_offset >= 64) {
98+
*byte_offset += 8;
99+
*bit_offset -= 64;
100+
101+
int bytes_remaining = max_bytes - *byte_offset;
102+
if (LIKELY(bytes_remaining >= 8)) {
103+
memcpy(buffered_values, buffer + *byte_offset, 8);
104+
} else {
105+
memcpy(buffered_values, buffer + *byte_offset, bytes_remaining);
106+
}
107+
108+
// Read bits of v that crossed into new buffered_values_
109+
*v |= BitUtil::TrailingBits(*buffered_values, *bit_offset)
110+
<< (num_bits - *bit_offset);
111+
DCHECK_LE(*bit_offset, 64);
112+
}
113+
}
114+
88115
template <typename T>
89116
inline bool BitReader::GetValue(int num_bits, T* v) {
117+
return GetBatch(num_bits, v, 1) == 1;
118+
}
119+
120+
template <typename T>
121+
inline int BitReader::GetBatch(int num_bits, T* v, int batch_size) {
90122
DCHECK(buffer_ != NULL);
91123
// TODO: revisit this limit if necessary
92124
DCHECK_LE(num_bits, 32);
93125
DCHECK_LE(num_bits, static_cast<int>(sizeof(T) * 8));
94126

95-
if (UNLIKELY(byte_offset_ * 8 + bit_offset_ + num_bits > max_bytes_ * 8)) return false;
96-
97-
*v = BitUtil::TrailingBits(buffered_values_, bit_offset_ + num_bits) >> bit_offset_;
98-
99-
bit_offset_ += num_bits;
100-
if (bit_offset_ >= 64) {
101-
byte_offset_ += 8;
102-
bit_offset_ -= 64;
127+
int bit_offset = bit_offset_;
128+
int byte_offset = byte_offset_;
129+
uint64_t buffered_values = buffered_values_;
130+
int max_bytes = max_bytes_;
131+
const uint8_t* buffer = buffer_;
132+
133+
uint64_t needed_bits = num_bits * batch_size;
134+
uint64_t remaining_bits = (max_bytes - byte_offset) * 8 - bit_offset;
135+
if (remaining_bits < needed_bits) { batch_size = remaining_bits / num_bits; }
136+
137+
int i = 0;
138+
if (UNLIKELY(bit_offset != 0)) {
139+
for (; i < batch_size && bit_offset != 0; ++i) {
140+
GetValue_(num_bits, &v[i], max_bytes, buffer, &bit_offset, &byte_offset,
141+
&buffered_values);
142+
}
143+
}
103144

104-
int bytes_remaining = max_bytes_ - byte_offset_;
105-
if (LIKELY(bytes_remaining >= 8)) {
106-
memcpy(&buffered_values_, buffer_ + byte_offset_, 8);
107-
} else {
108-
memcpy(&buffered_values_, buffer_ + byte_offset_, bytes_remaining);
145+
if (sizeof(T) == 4) {
146+
int num_unpacked = unpack32(reinterpret_cast<const uint32_t*>(buffer + byte_offset),
147+
reinterpret_cast<uint32_t*>(v + i), batch_size - i, num_bits);
148+
i += num_unpacked;
149+
byte_offset += num_unpacked * num_bits / 8;
150+
} else {
151+
const int buffer_size = 1024;
152+
static uint32_t unpack_buffer[buffer_size];
153+
while (i < batch_size) {
154+
int unpack_size = std::min(buffer_size, batch_size - i);
155+
int num_unpacked = unpack32(reinterpret_cast<const uint32_t*>(buffer + byte_offset),
156+
unpack_buffer, unpack_size, num_bits);
157+
if (num_unpacked == 0) { break; }
158+
for (int k = 0; k < num_unpacked; ++k) {
159+
v[i + k] = unpack_buffer[k];
160+
}
161+
i += num_unpacked;
162+
byte_offset += num_unpacked * num_bits / 8;
109163
}
164+
}
110165

111-
// Read bits of v that crossed into new buffered_values_
112-
*v |= BitUtil::TrailingBits(buffered_values_, bit_offset_)
113-
<< (num_bits - bit_offset_);
166+
int bytes_remaining = max_bytes - byte_offset;
167+
if (bytes_remaining >= 8) {
168+
memcpy(&buffered_values, buffer + byte_offset, 8);
169+
} else {
170+
memcpy(&buffered_values, buffer + byte_offset, bytes_remaining);
114171
}
115-
DCHECK_LE(bit_offset_, 64);
116-
return true;
172+
173+
for (; i < batch_size; ++i) {
174+
GetValue_(
175+
num_bits, &v[i], max_bytes, buffer, &bit_offset, &byte_offset, &buffered_values);
176+
}
177+
178+
bit_offset_ = bit_offset;
179+
byte_offset_ = byte_offset;
180+
buffered_values_ = buffered_values;
181+
182+
return batch_size;
117183
}
118184

119185
template <typename T>

0 commit comments

Comments
 (0)