Skip to content

Commit bc07b0b

Browse files
MatzeBfacebook-github-bot
authored andcommitted
perf(nimble): Add bulk-scan path to legacy fixed-width reads (#18612)
Summary: This diff is created by PerfAICT to optimize `facebook::nimble::legacy::callReadWithVisitor` in "fbcode/velox/dwio/nimble/encodings/legacy/EncodingUtils.h", by reducing CPU cycles spent in this function. ### Optimization Details The inclusive cost of `legacy::callReadWithVisitor` was dominated by `FixedBitWidthEncoding<T>::readWithVisitor`, which always decoded values one at a time via the scalar per-value slow path (`fixedBitArray_.get(row_++) + baseline_` followed by per-value `ColumnVisitor::process`). This change adds a bulk-scan fast path — mirroring the already-landed canonical implementation in the non-legacy `encodings/FixedBitWidthEncoding.h` — that handles the dominant dense, no-null, no-filter/hook, 4/8-byte integral ExtractToReader case with a single `bulkGetWithBaseline` decode plus `processFixedWidthRun`, falling back to the unchanged slow path for all other cases. All supporting helpers (`readWithVisitorFast`, `useFastPath`, `processFixedWidthRun`, `FixedBitArray`) are shared, not duplicated, so behavior is preserved. Differential Revision: D116602815
1 parent e5d4474 commit bc07b0b

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

velox/dwio/nimble/encodings/legacy/FixedBitWidthEncoding.h

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
#pragma once
1717

18+
#include <cstring>
1819
#include <span>
1920
#include <type_traits>
2021

@@ -64,6 +65,14 @@ class FixedBitWidthEncoding final
6465
template <typename DecoderVisitor>
6566
void readWithVisitor(DecoderVisitor& visitor, ReadWithVisitorParams& params);
6667

68+
template <bool kScatter, typename Visitor>
69+
void bulkScan(
70+
Visitor& visitor,
71+
vector_size_t currentRow,
72+
const vector_size_t* selectedRows,
73+
vector_size_t numSelected,
74+
const vector_size_t* scatterRows);
75+
6776
static std::string_view encode(
6877
EncodingSelection<physicalType>& selection,
6978
std::span<const physicalType> values,
@@ -131,6 +140,25 @@ template <typename V>
131140
void FixedBitWidthEncoding<T>::readWithVisitor(
132141
V& visitor,
133142
ReadWithVisitorParams& params) {
143+
using OutputType = detail::ValueType<typename V::DataType>;
144+
constexpr bool kIsSuitableWidth =
145+
(isFourByteIntegralType<physicalType>() ||
146+
isEightByteIntegralType<physicalType>());
147+
constexpr bool kIsFluidCast = sizeof(OutputType) >= sizeof(physicalType) &&
148+
std::is_integral_v<OutputType> && std::is_integral_v<physicalType>;
149+
// Limit bulk decoding to supported integral conversions.
150+
if constexpr (
151+
kIsSuitableWidth &&
152+
std::is_same_v<
153+
typename V::Extract,
154+
velox::dwio::common::ExtractToReader> &&
155+
kIsFluidCast) {
156+
auto* nulls = visitor.reader().rawNullsInReadRange();
157+
if (velox::dwio::common::useFastPath(visitor, nulls)) {
158+
detail::readWithVisitorFast(*this, visitor, params, nulls);
159+
return;
160+
}
161+
}
134162
detail::readWithVisitorSlow(
135163
visitor,
136164
params,
@@ -141,6 +169,106 @@ void FixedBitWidthEncoding<T>::readWithVisitor(
141169
});
142170
}
143171

172+
template <typename T>
173+
template <bool kScatter, typename V>
174+
void FixedBitWidthEncoding<T>::bulkScan(
175+
V& visitor,
176+
vector_size_t currentRow,
177+
const vector_size_t* selectedRows,
178+
vector_size_t numSelected,
179+
const vector_size_t* scatterRows) {
180+
using OutputType = detail::ValueType<typename V::DataType>;
181+
static_assert(
182+
isFourByteIntegralType<physicalType>() ||
183+
isEightByteIntegralType<physicalType>(),
184+
"bulkScan only supports 4-byte or 8-byte integral types");
185+
186+
if (numSelected == 0) {
187+
return;
188+
}
189+
190+
const auto numRows = visitor.numRows() - visitor.rowIndex();
191+
192+
// Translate logical rows to the encoding's non-null row space.
193+
const auto offset =
194+
static_cast<int32_t>(row_) - static_cast<int32_t>(currentRow);
195+
196+
auto* values = detail::mutableValues<OutputType>(visitor, numRows);
197+
198+
constexpr bool kSameSize = sizeof(physicalType) == sizeof(OutputType);
199+
constexpr bool kIsUpcast = sizeof(OutputType) > sizeof(physicalType) &&
200+
std::is_integral_v<OutputType> && std::is_integral_v<physicalType>;
201+
202+
if constexpr (V::dense) {
203+
if constexpr (isFourByteIntegralType<physicalType>()) {
204+
if constexpr (kSameSize) {
205+
buffer_.resize(numSelected);
206+
fixedBitArray_.bulkGetWithBaseline(
207+
selectedRows[0] + offset, numSelected, buffer_.data(), baseline_);
208+
std::memcpy(values, buffer_.data(), numSelected * sizeof(physicalType));
209+
} else if constexpr (kIsUpcast) {
210+
static_assert(isEightByteIntegralType<OutputType>());
211+
// Decode directly into the widened output.
212+
fixedBitArray_.bulkGetWithBaseline32Into64(
213+
selectedRows[0] + offset,
214+
numSelected,
215+
reinterpret_cast<uint64_t*>(values),
216+
baseline_);
217+
}
218+
} else {
219+
static_assert(isEightByteIntegralType<physicalType>());
220+
static_assert(kSameSize, "8-byte bulkScan requires same-size output");
221+
fixedBitArray_.bulkGetWithBaseline(
222+
selectedRows[0] + offset,
223+
numSelected,
224+
reinterpret_cast<physicalType*>(values),
225+
baseline_);
226+
}
227+
} else {
228+
for (vector_size_t i = 0; i < numSelected; ++i) {
229+
values[i] = static_cast<OutputType>(
230+
fixedBitArray_.get(selectedRows[i] + offset) + baseline_);
231+
}
232+
}
233+
234+
row_ += selectedRows[numSelected - 1] - currentRow + 1;
235+
236+
if constexpr (!kScatter && !V::kHasFilter && !V::kHasHook) {
237+
visitor.addNumValues(numRows);
238+
visitor.setRowIndex(visitor.numRows());
239+
return;
240+
}
241+
242+
// Apply scattering, filtering, or hooks after decoding.
243+
if constexpr (!V::kHasHook) {
244+
values = reinterpret_cast<OutputType*>(visitor.reader().rawValues());
245+
}
246+
247+
auto numValues = visitor.reader().numValues();
248+
int32_t* filterHits = nullptr;
249+
if constexpr (V::kHasFilter) {
250+
filterHits = visitor.outputRows(numSelected) - numValues;
251+
}
252+
253+
velox::dwio::common::
254+
processFixedWidthRun<OutputType, V::kFilterOnly, kScatter, V::dense>(
255+
velox::RowSet(selectedRows, numSelected),
256+
0,
257+
numSelected,
258+
scatterRows,
259+
values,
260+
filterHits,
261+
numValues,
262+
visitor.filter(),
263+
visitor.hook());
264+
265+
if constexpr (!V::kHasHook) {
266+
visitor.addNumValues(
267+
V::kHasFilter ? numValues - visitor.reader().numValues() : numRows);
268+
}
269+
visitor.setRowIndex(visitor.numRows());
270+
}
271+
144272
template <typename T>
145273
std::string_view FixedBitWidthEncoding<T>::encode(
146274
EncodingSelection<physicalType>& selection,

0 commit comments

Comments
 (0)