Skip to content

perf(parquet): batched DELTA decoding with SIMD bit-unpack#6

Open
jaylisde wants to merge 1 commit into
mainfrom
perf/delta-decoder-overhaul
Open

perf(parquet): batched DELTA decoding with SIMD bit-unpack#6
jaylisde wants to merge 1 commit into
mainfrom
perf/delta-decoder-overhaul

Conversation

@jaylisde

@jaylisde jaylisde commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Summary

Closes the DELTA-vs-PLAIN/DICT scan gap on TPC-H Q12 (DELTA-encoded lineitem) left after facebookincubator#17633. DELTA scan CPU was still ~4.5x of PLAIN/DICT; after this PR it's ~1.8x.

The work covers both DELTA-encoded readers in Velox: DeltaBpDecoder (used by DELTA_BINARY_PACKED integer columns and as a length decoder inside DELTA_LENGTH_BYTE_ARRAY / DELTA_BYTE_ARRAY) and DeltaByteArrayDecoder (string columns). Three layered changes:

  1. SIMD bit-unpack kernels for whole miniblocks at bit_width 0–32.
  2. A batched decode fast path that calls ColumnVisitor::processRun() once per chunk instead of per-row process(), with SIMD filter pushdown for deterministic integer filters.
  3. DeltaByteArrayDecoder micro-optimizations: devirtualization, decoder reuse across pages, and std::string → raw buffer.

PLAIN/DICT path is unchanged within noise.

Performance

TPC-H Q12 SF10, num_drivers=4 --num_repeats=5 --warmup_after_clear=true, 5-run median:

Wall lineitem scan CPU
baseline (facebookincubator#17633) 1.02 s 2.76 s
This PR 0.594 s 1.11 s
Δ −42% −60%

Implementation

1. SIMD Bit-Unpack Kernels

Compile-time dispatch on bit_width 0–32:

bit_width Strategy
0 Arithmetic-sequence fast path (all deltas identical)
1–16 4 values/iter via single unaligned 64-bit load + shift/mask
17–32 2 values/iter via __uint128_t funnel-shift (lowers to SHRD on x86_64)
33–64 Scalar inner loop (no SIMD batching: a single value would span > 64 bits after byte-misalignment, rare in practice)

2. Batched Decode Fast Path (processRun)

Instead of per-row visitor.process(), decode an entire miniblock into the output buffer and call visitor.processRun() once per chunk. Two fast paths inside:

  • AlwaysTrue (no filter): zero-copy, just advance the counter
  • Deterministic filter + integer types: SIMD batch comparison via processFixedFilter

3. Sparse Buffered Path

For filtered reads, decode kBatch=1024 physical values into a stack buffer (≤ 8 KB for int64_t, well within typical thread stack limits), then index into it by rows[k] - batchPhysStart. One sequential decode pass (cache-friendly) turns random row access into simple array indexing.

4. DeltaByteArrayDecoder Optimizations

  • Devirtualization: remove DeltaByteArrayDecoderBase, eliminate vtable dispatch from per-row readString()
  • Allocation reuse: child RleBpDecoder uses std::optional + reset() instead of make_unique per page (saves 3 heap allocations per page boundary)
  • Buffer optimization: replace std::string lastValue_ with std::vector<char> + length — eliminates std::string::_M_replace overhead (~6.5% in profiling) from prefix-share encoding

@jaylisde
jaylisde force-pushed the perf/delta-decoder-overhaul branch 6 times, most recently from 601bfd2 to a613dd9 Compare June 4, 2026 03:00
@jaylisde jaylisde changed the title perf(parquet): DELTA decoder overhaul — batched decode + SIMD bit-unpack perf(parquet): DELTA decoder overhaul - batched decode + SIMD bit-unpack Jun 4, 2026
@jaylisde jaylisde changed the title perf(parquet): DELTA decoder overhaul - batched decode + SIMD bit-unpack perf(parquet): DELTA decoder overhaul — batched decoding with SIMD bit-unpack Jun 4, 2026
@jaylisde
jaylisde force-pushed the perf/delta-decoder-overhaul branch from a613dd9 to 17d32a5 Compare June 4, 2026 06:42
@jaylisde jaylisde changed the title perf(parquet): DELTA decoder overhaul — batched decoding with SIMD bit-unpack perf(parquet): batched DELTA decoding with SIMD bit-unpack Jun 4, 2026
@jaylisde
jaylisde force-pushed the perf/delta-decoder-overhaul branch 9 times, most recently from 8deaa51 to b7e08fa Compare June 4, 2026 08:22
Closes the DELTA-vs-PLAIN/DICT scan-CPU gap on TPC-H Q12 left after
facebookincubator#17633. DELTA scan CPU was ~4.5x of PLAIN/DICT;
after this PR ~1.8x.

- DeltaBpDecoder: batched decode with one ColumnVisitor::processRun()
  per chunk; inline SIMD bit-unpack for whole bit-aligned miniblocks
  at bit_width 0..32; sparse-buffered path for filtered reads.
- ColumnVisitor: new base processRun with SIMD filter subpaths.
- DeltaByteArrayDecoder: devirtualize, reuse child RleBpDecoder across
  pages, replace std::string lastValue_ with raw char buffer.

Q12 SF10 (4 drivers, 5-run median):
  baseline (facebookincubator#17633): 1.02 s wall, 2.76 s lineitem scan CPU
  this PR:           0.594 s wall, 1.11 s lineitem scan CPU
                     −42%          −60%
@jaylisde
jaylisde force-pushed the perf/delta-decoder-overhaul branch from b7e08fa to a99db1e Compare June 4, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant