Skip to content

Filter compression schemes by serialized IDs instead of array IDs - #9914

Merged
mhk197 merged 2 commits into
developfrom
mk/scheme-serialized-ids
Sep 16, 2026
Merged

mhk197 merged 2 commits into
developfrom
mk/scheme-serialized-ids

Conversation

@mhk197

@mhk197 mhk197 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Compression schemes are filtered against the IDs a writer may emit. Today that filter mixes two ID spaces. Schemes declare their in-memory encoding ID, while most callers pass the serialized IDs from their enabled editions. The two only line up because every encoding currently uses the same string for both.

DecimalByteParts stops doing that in #9834. Its in-memory ID becomes vortex.decimal_byte_parts.v2 while the frozen wire ID stays vortex.decimal_byte_parts. With the current filter, every caller that passes edition IDs would silently drop the decimal scheme and write decimals canonical.

This PR makes the contract explicit. Schemes declare the serialized IDs they write, and every caller filters with serialized IDs.

Changes

  • The file writer no longer maps permitted serialized IDs through the registry to plugin in-memory IDs before filtering schemes. new_array_context returns only the array context, which is seeded with exactly the permitted IDs, and the writer reads them back with to_ids into a named set for the compressor builder. This matches the Python, TUI, CUDA, bench, and golden-test callers, which already pass serialized IDs.

Behavior

No scheme changes behavior. In-memory and wire IDs coincide for every scheme on develop, so the same schemes are retained from the same inputs and no snapshot changes.

Follow-ups

Signed-off-by: Matt Katz <mhkatz97@gmail.com>
@mhk197 mhk197 added the changelog/break A breaking API change label Sep 16, 2026
@codspeed

codspeed Bot commented Sep 16, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 9.72%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚡ 2 improved benchmarks
❌ 5 regressed benchmarks
✅ 2195 untouched benchmarks
⏩ 218 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
WallTime arrow_checked_add_u32_neon[16384] 12.3 µs 20.4 µs -39.91%
WallTime subtract_shapes_neon[(128, PerRowPerRow)] 1.8 µs 2 µs -10.77%
Simulation allocate_drop_vortex_minimal_alignment[0] 913.3 ns 1,021.7 ns -10.6%
WallTime multiply_shapes_neon[(128, PerRowPerRow)] 1.9 µs 2.1 µs -10.34%
Simulation allocate_drop_vortex[0] 969.2 ns 1,077.5 ns -10.05%
WallTime filtered_sink_i64_avx2[OneNullInEight] 26 µs 22.9 µs +13.65%
WallTime filtered_sink_i64_avx512[OneNullInEight] 25.6 µs 23 µs +11.23%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing mk/scheme-serialized-ids (d308072) with develop (96b2684)2

Open in CodSpeed

Footnotes

  1. 218 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on develop (a8e949e) during the generation of this report, so 96b2684 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

…array context

Signed-off-by: Matt Katz <mhkatz97@gmail.com>
@mhk197
mhk197 marked this pull request as ready for review September 16, 2026 16:14
@mhk197
mhk197 requested review from joseph-isaacs and robert3005 and removed request for robert3005 September 16, 2026 16:14
@mhk197 mhk197 changed the title Filter compression schemes by the serialized IDs they write Filter compression schemes by the serialized IDs instead of array IDs Sep 16, 2026
@mhk197 mhk197 changed the title Filter compression schemes by the serialized IDs instead of array IDs Filter compression schemes by serialized IDs instead of array IDs Sep 16, 2026
Comment thread vortex-file/src/writer.rs
Comment on lines +250 to +251
let allowed_serialized_ids: HashSet<ArrayId> =
ctx.array_ctx().to_ids().into_iter().collect();

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.

I was trying very hard to not create it twice but I recognise this is over optimising

@mhk197
mhk197 merged commit 4dd66a2 into develop Sep 16, 2026
92 of 93 checks passed
@mhk197
mhk197 deleted the mk/scheme-serialized-ids branch September 16, 2026 17:31
mhk197 added a commit that referenced this pull request Sep 16, 2026
…#9917)

## Summary

#9914 stopped mapping the writer's permitted serialized IDs through the
array registry before filtering compression schemes. That mapping had a
side effect on the scheme filter only: an edition-enabled ID with no
registered plugin produced `None` in the `filter_map` and was excluded
from the set handed to the compressor. The array context never had that
filter. It seeded and permitted every enabled ID whether or not a plugin
was registered.

This PR filters at the source, so both the scheme set and the array
context exclude enabled IDs that nothing on the session could serialize.

## Changes

With editions enforced, `new_array_context` now keeps only the enabled
array IDs that have a registered plugin. That set seeds the file's
encoding table and, through `to_ids`, restricts the compressor. Without
edition enforcement the set comes from the registry itself and needs no
filter.

## Behavior

Before #9914, a session that enabled an edition but did not register one
of its encodings never selected that encoding's compression scheme.
After #9914 the scheme could be selected, and the write then failed in
`array_serialize` with "not registered for serialization". This PR
returns to skipping the scheme.

Excluding those IDs from the array context is new behavior. An
unregistered encoding can never be serialized, since `array_serialize`
fails before any interning, so its entry in the file's encoding table
was never used. The footer changes only for sessions in that
partial-registry state. Every shipped writer registers all encodings, so
none are affected.

## Testing

A new test enables an edition listing `vortex.primitive` and
`vortex.alp` on a bare array session, where ALP is not registered, and
checks that only the primitive ID is seeded and ALP cannot be interned.

---------

Signed-off-by: Matt Katz <mhkatz97@gmail.com>
mhk197 added a commit that referenced this pull request Sep 18, 2026
)

## Summary

`DecimalBytePartsArray` stored the whole unscaled decimal value in one
signed integer child. That capped it at values that fit in 64 bits. This
PR adds support for `i128` and `i256` decimals.

Each value is now split into a signed most significant part (MSP) plus
up to three unsigned 64-bit lower parts. Every part is an independent
child array, so each one compresses on its own. The frozen
`vortex.decimal_byte_parts` file format is untouched. Arrays with lower
parts serialize under a new `vortex.decimal_byte_parts.v2` format owned
by a plugin.

This is the integration branch for three reviewed sub-PRs: #9808
(splitting and assembly), #9809 (array and kernels), and #9810 (serde
plugin).

## Representation

| Decimal storage | Children |
| --- | --- |
| `i8`, `i16`, `i32`, `i64` | Signed MSP only. Shares the original value
buffer. |
| `i128` | `i64` MSP holding the high 64 bits, plus one `u64` lower
part. |
| `i256` | `i64` MSP holding the high 64 bits, plus three `u64` lower
parts. |

Parts are ordered most significant first. The MSP carries the sign and
the null mask. Lower parts are non-nullable unsigned integers. A lower
part may use a narrower dtype such as `u8`, `u16`, or `u32` when its
values fit. Its position still counts as a full 64-bit window. Splitting
writes zeroes at null positions so stray bytes in null slots do not hurt
compression of the lower parts.

## Splitting and assembly

`DecimalByteParts::encode` splits a `DecimalArray` into parts.
`split_decimal` exposes the raw parts for callers that want to build the
array themselves.

Assembly picks a path from the number of lower parts:

- **None.** Reuse the MSP buffer as decimal storage without copying.
- **One.** Combine the MSP and the lower part into an `i128`.
- **Two.** The lower parts form the low 128 bits of an `i256`. The MSP
is sign-extended into the high 128 bits.
- **Three.** The MSP and the first lower part form the high 128 bits.
The remaining two form the low 128 bits.

Assembly casts narrowed lower parts back to `u64` first. The `i256`
assembly loop vectorizes on local ARM64 builds. Marking `i256`'s shifts
`#[inline]` removed three out-of-line calls per row.

| Rows | With `#[inline]` | Without |
| --- | --- | --- |
| 1,024 | 0.917 µs | 6.207 µs |
| 8,192 | 6.332 µs | 48.540 µs |

Medians of five alternating release runs. `From<i64>` and `From<u64>`
for `i256` are added in `vortex-array`.

## Compute

`execute::<DecimalArray>` reassembles the canonical array from all
parts. The compare, filter, is-constant, and take kernels understand
lower parts. Slice and mask apply per child.

Two limits are documented in code. Take with nullable indices on an
array with lower parts falls back to canonical execution, because taking
each part would make the lower parts nullable. The CUDA kernel rejects
arrays with lower parts, because GPU reassembly is not implemented yet.

## Serialization

`DecimalBytePartsPlugin` owns both wire formats and picks one from the
array layout.

| Array layout | Serialized ID |
| --- | --- |
| MSP only | `vortex.decimal_byte_parts` |
| MSP plus one to three lower parts | `vortex.decimal_byte_parts.v2` |

The v1 format is frozen. Its metadata and decoder live in `plugin/v1.rs`
and are byte-identical to what shipped. The v1 decoder rejects any
payload that claims lower parts.

The v2 format records the MSP's integer type and one integer type per
lower part. The lower part count is the length of that list. The decoder
validates every type and restores each child with its recorded dtype.
The v2 format itself accepts zero lower parts. The plugin only chooses
it when lower parts are present, so files stay readable by older readers
whenever possible.

The in-memory encoding ID is now `vortex.decimal_byte_parts.v2`. The
registry maps both wire IDs to the plugin, so existing v1 files read
through it with no migration.

No edition declares the v2 format yet. Writing an array with lower parts
under an edition that does not permit v2 fails with an explicit error
rather than silently falling back.

## Compression

The BtrBlocks decimal scheme still narrows decimals that fit in `i64`
and wraps them in a single-part array. Wide decimals stay canonical.
Nothing in this PR writes the v2 format through the compressor.

The scheme now declares `vortex.decimal_byte_parts` as its produced
encoding rather than the in-memory ID. Since #9914 that list holds the
serialized IDs a scheme writes, and this scheme only ever writes the
frozen format. Without that change every writer that filters schemes by
edition would drop the decimal scheme, because no edition permits the
in-memory v2 name.

## API Changes

**Breaking.** Registering `DecimalByteParts` directly no longer supports
serde for either format. Replace
`session.arrays().register(DecimalByteParts)` with
`session.arrays().register(DecimalBytePartsPlugin)`.
`vortex_decimal_byte_parts::initialize` already does this.

**Breaking.** `dbp_encode` is replaced by `DecimalByteParts::encode`.

**Breaking.** `DecimalBytesPartsMetadata` is no longer public.
`DecimalBytePartsV2Metadata` is exposed instead.

The in-memory encoding ID string changed from
`vortex.decimal_byte_parts` to `vortex.decimal_byte_parts.v2`. This
affects display and trace output, not files.

New public items: `DecimalByteParts::try_new_with_lower_parts`,
`DecimalByteParts::encode`, `split_decimal`, `DecimalParts`,
`DecimalBytePartsPlugin`, `decimal_byte_parts_v1_id`, and
`decimal_byte_parts_v2_id`.

---------

Signed-off-by: "Matt Katz" <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/break A breaking API change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants