Skip to content

Commit f7f27f4

Browse files
Yuhtafacebook-github-bot
authored andcommitted
fix(nimble): Keep nulls in dictionary reads without AVX2 (#18621)
Summary: Reading a Nimble string column that keeps its dictionary encoding dropped every null on any platform where `process::hasAvx2()` is false, which is all of aarch64. A 200-row column holding `always_the_same` with a null every 7th row came back as a `DictionaryVector` carrying no null flags, so row 0 read as `always_the_same` instead of null. The dense dictionary-index path wrote nulls only into the reader's read-range bitmap. It then relied on `returnReaderNulls_` to hand that bitmap back from `resultNulls()`. `setReturnNullsMode` clears that flag whenever `useBulkPath()` is false, and `process::hasAvx2()` makes that permanent off AVX2. `resultNulls()` then returns the output-indexed buffer instead, which nothing on this path filled. It now copies the read-range nulls into that buffer, the way the sparse row-set path already did. Reviewed By: vandreykiv Differential Revision: D116833280
1 parent a9c90e0 commit f7f27f4

13 files changed

Lines changed: 164 additions & 107 deletions

File tree

velox/dwio/nimble/encodings/CMakeLists.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ add_library(
8383
SparseBoolEncoding.h
8484
SimdForBitpackEncoding.h
8585
SharedDictionaryEncoding.h
86+
SharedDictionaryCatalog.h
8687
SharedDictionaryBuilder.h
8788
SentinelEncoding.h
8889
RLEEncoding.h
@@ -134,10 +135,7 @@ target_include_directories(
134135
nimble_shared_dictionary_fb
135136
INTERFACE ${PROJECT_BINARY_DIR} ${FLATBUFFERS_INCLUDE_DIR}
136137
)
137-
add_dependencies(
138-
nimble_shared_dictionary_fb
139-
nimble_shared_dictionary_schema_fb
140-
)
138+
add_dependencies(nimble_shared_dictionary_fb nimble_shared_dictionary_schema_fb)
141139

142140
target_link_libraries(
143141
nimble_encodings

velox/dwio/nimble/encodings/common/Encoding.h

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,22 @@ struct ReadWithVisitorParams {
8989
// Create the reader nulls buffer if not already exists and return pointer to
9090
// the raw buffer. When it is created, it is created with the full length
9191
// across potential multiple chunks.
92-
std::function<uint64_t*()> makeReaderNulls;
92+
std::function<uint64_t*()> makeReaderNulls{};
9393

9494
// Resolves which buffer `SelectiveColumnReader::resultNulls()' returns for
9595
// this read by setting the `returnReaderNulls_' flag (and `anyNulls_').
9696
// Resolves the flag only; allocates no buffer. Must be called after decoding
9797
// nulls in `NullableEncoding'.
98-
std::function<void()> setReturnNullsMode;
98+
std::function<void()> setReturnNullsMode{};
9999

100100
// Create the result nulls if not already exists. Similar to
101101
// `makeReaderNulls', we create one single buffer for all the results nulls
102102
// across potential multiple chunks during one read.
103-
std::function<void()> prepareResultNulls;
103+
std::function<void()> prepareResultNulls{};
104104

105105
// Number of rows scanned so far. Contains rows scanned in previous chunks
106106
// during this read call as well.
107-
vector_size_t numScanned;
107+
vector_size_t numScanned{};
108108
};
109109

110110
class Encoding {
@@ -162,7 +162,7 @@ class Encoding {
162162

163163
/// Direct alphabet for SharedDictionary encodings when the read path has
164164
/// already resolved the dictionary bound to this value stream.
165-
std::shared_ptr<const SharedDictionaryAlphabet> sharedDictionaryAlphabet;
165+
std::shared_ptr<const SharedDictionaryAlphabet> sharedDictionaryAlphabet{};
166166

167167
velox::io::IoCounter* decompressCounter() const {
168168
return decodingStats != nullptr ? &decodingStats->decompressCPUTimeNanos
@@ -527,6 +527,25 @@ void readDenseMaterializedIndices(
527527
/*sourceBegin=*/valueOutputOffset,
528528
rawOuterNonNullRows,
529529
rawOutputValues);
530+
531+
// Nulls were materialized into the reader's read-range bitmap only.
532+
// `resultNulls()' hands that bitmap back while `returnReaderNulls_' holds,
533+
// but `setReturnNullsMode' clears the flag whenever `useBulkPath()' is false
534+
// -- notably on a platform without AVX2, where every read takes that branch.
535+
// `resultNulls()' then returns the output-indexed `resultNulls_', which
536+
// nothing on this path writes, so the output would silently lose its nulls.
537+
// Copy the read-range nulls across, mirroring what
538+
// `readSparseMaterializedIndices' does for the sparse row set.
539+
if (!visitor.reader().returnReaderNulls()) {
540+
auto* rawResultNulls = visitor.reader().rawResultNulls();
541+
NIMBLE_CHECK_NOT_NULL(
542+
rawResultNulls,
543+
"prepareResultNulls must allocate result nulls before the dense index "
544+
"path writes them");
545+
velox::bits::copyBits(
546+
rawNulls, readOffset, rawResultNulls, valueOutputOffset, numReadRows);
547+
visitor.reader().setHasNulls();
548+
}
530549
visitor.addNumValues(numReadRows);
531550
visitor.setRowIndex(visitor.numRows());
532551
}

velox/dwio/nimble/encodings/tests/ReadWithVisitorTest.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4238,9 +4238,16 @@ TEST_P(ReadWithVisitorNonLegacyTest, readDenseMaterializedIndicesWithNulls) {
42384238

42394239
ASSERT_EQ(reader->numValues(), 0);
42404240

4241-
// Call the helper with nulls.
4241+
// Call the helper with nulls. prepareResultNulls must mirror what
4242+
// ChunkedDecoder wires up in production: the dense index path materializes
4243+
// nulls into the read-range bitmap only, so it needs an allocated,
4244+
// output-indexed result-nulls buffer to copy them into whenever
4245+
// returnReaderNulls_ is false -- as it is here, the scan spec carries a
4246+
// filter.
42424247
ReadWithVisitorParams params{.numScanned = 0};
4243-
params.prepareResultNulls = [] {};
4248+
params.prepareResultNulls = [&] {
4249+
reader->prepareNulls(rows, /*hasNulls=*/true, /*extraRows=*/8);
4250+
};
42444251
detail::readDenseMaterializedIndices(
42454252
*encoding,
42464253
visitor,

velox/dwio/nimble/serializer/benchmarks/DeserializerBenchmark.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ constexpr size_t kNumColumns = 100;
3333
constexpr size_t kProjectionStride = 20;
3434

3535
struct BenchmarkState {
36-
std::string serialized;
37-
std::shared_ptr<const Type> schema;
38-
std::vector<Deserializer::Subfield> selectedSubfields;
36+
std::string serialized{};
37+
std::shared_ptr<const Type> schema{};
38+
std::vector<Deserializer::Subfield> selectedSubfields{};
3939
};
4040

4141
BenchmarkState prepareBenchmark() {

velox/dwio/nimble/serializer/benchmarks/StreamSlicerBenchmark.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,14 @@ DEFINE_bool(
9494
using Subfield = velox::common::Subfield;
9595

9696
struct BenchmarkState {
97-
std::shared_ptr<velox::memory::MemoryPool> pool;
98-
std::string serialized;
99-
std::shared_ptr<const Type> schema;
100-
std::shared_ptr<const Type> projectedSchema;
101-
std::vector<Subfield> selectedSubfields;
102-
std::unique_ptr<Projector> projector;
103-
std::unique_ptr<Deserializer> deserializer;
104-
std::string encodingName;
97+
std::shared_ptr<velox::memory::MemoryPool> pool{};
98+
std::string serialized{};
99+
std::shared_ptr<const Type> schema{};
100+
std::shared_ptr<const Type> projectedSchema{};
101+
std::vector<Subfield> selectedSubfields{};
102+
std::unique_ptr<Projector> projector{};
103+
std::unique_ptr<Deserializer> deserializer{};
104+
std::string encodingName{};
105105
};
106106

107107
velox::vector_size_t numRows() {

velox/dwio/nimble/tablet/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ add_library(
190190
TabletReader.cpp
191191
TabletReader.h
192192
StripeGroup.h
193+
SharedDictionaryReader.h
193194
FileLayout.h
194195
)
195196
target_link_libraries(

velox/dwio/nimble/tablet/TabletReaderCache.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class TabletReaderCache {
140140

141141
/// Executor for background IO during TabletReader construction
142142
/// (e.g., parallel metadata loading).
143-
std::shared_ptr<folly::Executor> executor;
143+
std::shared_ptr<folly::Executor> executor{};
144144

145145
/// Observe the lifetime of each cached tablet, which is the only way to
146146
/// reach the metadata and index IO it does. onCreate runs once the entry
@@ -151,8 +151,8 @@ class TabletReaderCache {
151151
///
152152
/// A throw from onCreate propagates to the caller. A throw from onRelease
153153
/// cannot: it is invoked from a destructor, so it is logged and swallowed.
154-
std::function<void(const CachedTabletReader&)> onCreate;
155-
std::function<void(const CachedTabletReader&)> onRelease;
154+
std::function<void(const CachedTabletReader&)> onCreate{};
155+
std::function<void(const CachedTabletReader&)> onRelease{};
156156

157157
std::string toString() const {
158158
return fmt::format(

velox/dwio/nimble/tools/CMakeLists.txt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,7 @@ target_include_directories(
2929
nimble_external_dictionary_fb
3030
INTERFACE ${PROJECT_BINARY_DIR} ${FLATBUFFERS_INCLUDE_DIR}
3131
)
32-
add_dependencies(
33-
nimble_external_dictionary_fb
34-
nimble_external_dictionary_schema_fb
35-
)
32+
add_dependencies(nimble_external_dictionary_fb nimble_external_dictionary_schema_fb)
3633

3734
add_library(nimble_dump_lib NimbleDumpLib.cpp NimbleDumpLib.h)
3835
target_link_libraries(
@@ -73,7 +70,11 @@ target_link_libraries(
7370
velox_memory
7471
)
7572

76-
add_library(nimble_external_dictionary_builder ExternalDictionaryBuilder.cpp)
73+
add_library(
74+
nimble_external_dictionary_builder
75+
ExternalDictionaryBuilder.cpp
76+
ExternalDictionaryBuilder.h
77+
)
7778
target_link_libraries(
7879
nimble_external_dictionary_builder
7980
nimble_external_dictionary_fb

velox/dwio/nimble/tools/ExternalDictionaryBuilder.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ class ExternalDictionaryBuilder {
5757

5858
/// Optional forced alphabet encoding. When unset, readFactors or default
5959
/// encoding selection picks the alphabet encoding.
60-
std::optional<EncodingType> alphabetEncoding;
60+
std::optional<EncodingType> alphabetEncoding{};
6161

6262
/// Optional parsed manual read factors used by encoding selection.
63-
std::vector<std::pair<EncodingType, float>> readFactors;
63+
std::vector<std::pair<EncodingType, float>> readFactors{};
6464
};
6565

6666
explicit ExternalDictionaryBuilder(velox::memory::MemoryPool* pool);

velox/dwio/nimble/tools/tests/CMakeLists.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ target_link_libraries(
2424
gtest_main
2525
)
2626

27-
add_executable(
28-
external_dictionary_builder_tests
29-
ExternalDictionaryBuilderTest.cpp
30-
)
27+
add_executable(external_dictionary_builder_tests ExternalDictionaryBuilderTest.cpp)
3128
add_test(external_dictionary_builder_tests external_dictionary_builder_tests)
3229
target_link_libraries(
3330
external_dictionary_builder_tests

0 commit comments

Comments
 (0)