Skip to content

perf(deletion-vector): avoid payload copies and bitmap reserialization - #4181

Merged
kevinjqliu merged 3 commits into
apache:mainfrom
ArslanAnjum:perf/optimize-deletion-vector-deserialization
Nov 13, 2026
Merged

kevinjqliu merged 3 commits into
apache:mainfrom
ArslanAnjum:perf/optimize-deletion-vector-deserialization

Conversation

@ArslanAnjum

@ArslanAnjum ArslanAnjum commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Rationale for this change

Repeatedly slicing the remaining deletion-vector payload copies data for every bitmap. Use a memoryview and an offset to avoid those copies, and advance using PyRoaring's portable serialized size (__sizeof__()) instead of serializing each decoded bitmap again.

Require PyRoaring >=1.1.0, which accepts memoryview input. Keep byte widths explicit and cover mixed bitmap containers, missing keys, and parsing without reserialization.

Local synthetic benchmarks using the original description's generators, PyRoaring 1.1.0, and median timings across five repeated timeit batches show the additional improvement over the memoryview-only implementation:

Scenario Payload bytes Memoryview only With size lookup Speedup
Small sparse 1,108 3.23 us 2.84 us 1.14x
Sparse keys with gaps 102,008 108.60 us 98.33 us 1.10x
Medium 410,608 56.97 us 43.39 us 1.31x
Large dense 3,282,408 332.33 us 235.71 us 1.41x

These are local microbenchmark results, not end-to-end query speedups. Input slicing is zero-copy; bitmap deserialization still allocates decoded bitmaps.

Are these changes tested?

  • make lint — passed.
  • uv run --no-sync python -m pytest -m unit tests/table/test_deletion_vector.py -q — 13 passed with PyRoaring 1.1.0.
  • uv run --no-sync --default-index https://pypi.org/simple --with pyroaring==1.2.0 python -m pytest -m unit tests/table/test_deletion_vector.py -q — 13 passed with PyRoaring 1.2.0.
  • make test — blocked during collection because the optional Hive dependency thrift is missing locally.

Are there any user-facing changes?

No intended changes to decoded row positions or public APIs. The minimum PyRoaring version increases from 1.0.0 to 1.1.0.

Replace repeated byte string slicing with pointer offset tracking
via Python's built-in memoryview. This avoids O(N^2) memory buffer
allocations during parsing, improving throughput by up to 39x on
large payload sizes.
@ArslanAnjum
ArslanAnjum marked this pull request as ready for review October 29, 2026 18:56
Use PyRoaring's portable serialized size and require memoryview-compatible versions. Cover mixed bitmap encodings and key gaps without reserialization.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@kevinjqliu kevinjqliu changed the title perf(deletion-vector): use zero-copy memoryview for deserialization perf(deletion-vector): avoid payload copies and bitmap reserialization Nov 13, 2026
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@kevinjqliu kevinjqliu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! nice optimization

i cleaned up the code and pr description.

pyroaring needs >= 1.1 to use memoryview so i bump the version

number_of_bitmaps = int.from_bytes(pl[0:8], byteorder="little")
pl = pl[8:]
def _deserialize_bitmap(payload: bytes) -> list[BitMap]:
payload_view = memoryview(payload)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using memoryview is the main optimization here: it lets us advance through the payload without repeatedly copying the remaining bytes.

bitmaps.append(bm)
bitmap = BitMap.deserialize(payload_view[offset:])
# PyRoaring's __sizeof__ returns the portable serialized size without allocating bytes.
offset += bitmap.__sizeof__()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another optimization: __sizeof__() uses the same size calculation as serialize(), without allocating or serializing bytes.

Comment thread pyproject.toml
"pyparsing>=3.1.0",
"tenacity>=8.2.3",
"pyroaring>=1.0.0",
"pyroaring>=1.1.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requires PyRoaring ≥1.1.0 for memoryview support; see the updated deserialize() signature.

@kevinjqliu
kevinjqliu enabled auto-merge November 13, 2026 21:45
@kevinjqliu
kevinjqliu added this pull request to the merge queue Nov 13, 2026
Merged via the queue into apache:main with commit 0a2c3d2 Nov 13, 2026
20 checks passed
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.

2 participants