Skip to content

[WIP][POC] Delta binary packed coalesce miniblocks - #51250

Draft
prtkgaur wants to merge 2 commits into
apache:mainfrom
prtkgaur:delta-binary-packed-coalesce-miniblocks
Draft

[WIP][POC] Delta binary packed coalesce miniblocks#51250
prtkgaur wants to merge 2 commits into
apache:mainfrom
prtkgaur:delta-binary-packed-coalesce-miniblocks

Conversation

@prtkgaur

@prtkgaur prtkgaur commented Sep 9, 2026

Copy link
Copy Markdown

Rationale for this change

The miniblocks of a DELTA_BINARY_PACKED block are packed back to back with no padding between
them, so a run of miniblocks that share a bit width is bit-identical to a single longer run at
that width. GetInternal calls the bit unpacker once per miniblock all the same, which with
the default geometry is one call per 32 values -- at narrow widths, mostly per-call setup.

What changes are included in this PR?

A look-ahead over the block's stored bit widths, in a small private helper, reports how many of
the following miniblocks may be folded into the current unpack call, so a run of four asks the
unpacker for 128 values instead of 32. A miniblock joins the run only when its stored width
equals the current delta_bit_width_, which InitMiniBlock has already validated, and the
run also stops at the end of the block and at what the caller has room for.

Are these changes tested?

A new typed test covers the width patterns that decide where a run starts and stops, and the
fixture's read batch sizes gain one that stops partway through a coalesced run, so the existing
round trips cover it too. Three mutations of the implementation -- ignoring the neighbour's
width, ignoring the caller's room, and failing to advance the block cursor -- each turn the new
test red.

Benchmark

Same setup as the previous PR: AWS Graviton4, GCC 11.5, Release, one core, 9 repetitions,
medians, 65,536 values, both arms built and measured in one sitting. The two Fixed arms are
the delta_bit_width_ == 0 path, which has no unpack call to coalesce.

benchmark previous commit this PR vs. main
Decode_Int32_Narrow 78.11 us 58.61 us 1.33x 1.70x
Decode_Int32_Wide 80.79 us 65.08 us 1.24x 1.57x
Decode_Int64_Narrow 64.41 us 55.08 us 1.17x 1.49x
Decode_Int64_Wide 308.65 us 301.60 us 1.02x 1.06x
Decode_Int32_Fixed 19.75 us 20.31 us 0.97x 0.97x
Decode_Int64_Fixed 32.20 us 31.74 us 1.01x 0.97x

Are there any user-facing changes?

No. No API change, no format change, and decoded values are identical.

This PR includes breaking changes to public APIs. (If there are any breaking changes to public APIs, please explain which changes are breaking. If not, you can remove this.)

This PR contains a "Critical Fix". (If the changes fix either (a) a security vulnerability, (b) a bug that caused incorrect or invalid data to be produced, or (c) a bug that causes a crash (even when the API contract is upheld), please provide explanation. If not, you can remove this.)

min_delta_ and last_value_ have the same type as GetInternal's output
buffer, and that buffer points into memory the caller owns, so the
compiler cannot prove the prefix-sum store does not land on either
member: it reloads the frame and stores the running value on every
value. On aarch64 with GCC 11.5 the loop body is 8 instructions with 4
memory operations per value, where 6 and 2 are enough.

Hold the running value and the frame in locals across the loop and write
last_value_ back once when it ends. The arithmetic is unchanged - every
term stays in the unsigned type, so the wrapping the existing comment
documents is preserved and decoded values are identical. On the
DELTA_BINARY_PACKED decode benchmarks already in the tree this is 1.26x
to 1.29x wherever the running sum is a meaningful share of the work.
…ne call

The miniblocks of a DELTA_BINARY_PACKED block are packed back to back
with no padding between them, so a run of miniblocks that share a bit
width is bit-identical to a single longer run at that width. GetInternal
called the bit unpacker once per miniblock all the same, which with the
default geometry is one call per 32 values - mostly per-call setup.

Look ahead over the block's stored bit widths and extend the current
call over each following miniblock that has the same width and that the
caller has room for in full. A miniblock joins the run only when its
width equals the current delta_bit_width_, which InitMiniBlock has
already validated, and the run also stops at the end of the block. Add a
test over the width patterns that decide where a run starts and stops,
and read at a batch size that stops partway through a coalesced run.

On the decode benchmarks already in the tree this is 1.17x to 1.33x on
top of the previous commit wherever the unpacker's per-call cost is a
meaningful share of the work. Decoded values are identical; no encoded
byte changes.
@prtkgaur prtkgaur changed the title Delta binary packed coalesce miniblocks [WIP][POC] Delta binary packed coalesce miniblocks Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Thanks for opening a pull request!

This pull request has been automatically converted to a draft because its title doesn't match Arrow's required format.

If this is not a minor PR. Could you open an issue for this pull request on GitHub? https://github.com/apache/arrow/issues/new/choose

Opening GitHub issues ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename the pull request title in the following format?

GH-${GITHUB_ISSUE_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

After updating the title, you can mark the pull request as ready for review.

See also:

@github-actions github-actions Bot added the awaiting review Awaiting review label Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting review Awaiting review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants