Skip to content

GH-3772: Bulk skip in RunLengthBitPackingHybridDecoder / DictionaryValuesReader - #3773

Open
abstractdog wants to merge 1 commit into
apache:masterfrom
abstractdog:probe-decode-support-parquet-java
Open

GH-3772: Bulk skip in RunLengthBitPackingHybridDecoder / DictionaryValuesReader#3773
abstractdog wants to merge 1 commit into
apache:masterfrom
abstractdog:probe-decode-support-parquet-java

Conversation

@abstractdog

@abstractdog abstractdog commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

ValuesReader.skip(int n) defaults to a naive loop of skip() calls. For dictionary-encoded columns each skip() bottoms out in RunLengthBitPackingHybridDecoder.readInt() — a mode switch, array indexing, and a value the caller immediately discards. Any filter-then-skip path (column-index row ranges, hash-join probe filtering, runtime filters) pays this cost per skipped row.

RleSkipBenchmark, JMH throughput, JDK 17, 100 k values/op:

pattern bitWidth readInt() loop skipInts speedup
rle 8 1.38 B/s 104.9 B/s ~76×
packed 8 0.89 B/s 4.12 B/s ~4.6×
mixed 8 0.96 B/s 6.99 B/s ~7.2×

What changes are included in this PR?

  • New RunLengthBitPackingHybridDecoder.skipInts(int) — re-uses readNext() per run, then advances currentCount by min(n, currentCount) instead of walking every value through readInt().
  • skip(int) overrides on DictionaryValuesReader and RunLengthBitPackingHybridValuesReader delegating to decoder.skipInts(n).
  • parquet-benchmarks / RleSkipBenchmark JMH benchmark.

readNext() and the ValuesReader.skip(int) default are unchanged.

Are these changes tested?

TestRunLengthBitPackingHybridDecoderSkip covers RLE-only, PACKED-only, mixed, zero-skip, full-skip, mid-run partial skip, bitWidth = 0, and a randomised 4 K-value read/skip interleaving cross-checked against a readInt() reference. All parquet-column tests pass.

Are there any user-facing changes?

No. Additive and binary-compatible: no signatures change; skipInts(n) is semantically identical to N discarded readInt()s. Existing callers of ValuesReader.skip(int) pick up the fast path with no code change.

@abstractdog abstractdog changed the title GH-3772: Bulk skip in RunLengthBitPackingHybridDecoder / luesReader GH-3772: Bulk skip in RunLengthBitPackingHybridDecoder / DictionaryValuesReader Sep 4, 2026
…naryValuesReader

Add skipInts(int n) to RunLengthBitPackingHybridDecoder. It re-uses the
existing readNext() to load each run and then advances currentCount by
min(n, currentCount) instead of returning values one-by-one via readInt().
Runs are decoded the same way as before -- the win is dropping the
per-value mode switch, array-index arithmetic and method-call overhead
that readInt() pays for each value the caller is going to throw away.

Propagate to the two ValuesReader wrappers (DictionaryValuesReader,
RunLengthBitPackingHybridValuesReader) so callers going through the
public ValuesReader.skip(int) contract get the fast path.

Motivation: dictionary-encoded columns are ubiquitous in production
Parquet, and the default ValuesReader.skip(int) is a naive loop over
skip() -- which for dict columns is a RunLengthBitPackingHybridDecoder
readInt(). Filter-driven read paths (column-index row ranges, Hive
ProbeDecode, arbitrary row-skip) pay that cost per skipped row even when
the values are being thrown away.

Bench (parquet-benchmarks / RleSkipBenchmark, thrpt, 1 fork, 3x1s warmup,
5x1s measure, 100k values/op, ops/s of individual values):

  pattern=rle    bitWidth=8:  1.38 B/s ->  103.2 B/s   (~75x)
  pattern=packed bitWidth=8:  0.89 B/s ->   4.12 B/s   (~4.6x)
  pattern=mixed  bitWidth=8:  0.96 B/s ->   6.85 B/s   (~7.1x)

RLE runs dominate because a whole run is consumed with a single
currentCount decrement; bit-packed runs still get fully unpacked, so the
gain there is just the readInt() overhead avoided per value.

Tests: TestRunLengthBitPackingHybridDecoderSkip covers RLE-only,
PACKED-only, mixed, zero-skip, full-skip, partial-run skip, bitWidth=0,
and randomized skip/read alternation across 4K values. All 700
parquet-column tests still pass.
@abstractdog
abstractdog force-pushed the probe-decode-support-parquet-java branch from 5179bd9 to f3246b5 Compare September 4, 2026 19:31
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