Skip to content

Push decimal comparisons, between, and fills through Widen - #11003

Merged
connortsui20 merged 1 commit into
developfrom
ct/decimal-widen-compare
Nov 3, 2026
Merged

connortsui20 merged 1 commit into
developfrom
ct/decimal-widen-compare

Conversation

@connortsui20

@connortsui20 connortsui20 commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Tracking Issue: #10701

Based on develop, including the merged #10946. #11004 builds on it. No release gate.

Summary

Comparisons, bounds checks and fills otherwise widen every decimal value before doing their work. The child's precision already bounds its values, so these operations can often stay narrow or answer a comparison without reading the buffer.

Changes

Constants that fit use the child dtype, and constants outside its precision answer comparisons from the range bound. Two decimal children compare at their larger precision. Dtype matches select the compatible types, then integer and decimal kernels share casts and execution. A decimal fill outside the child precision falls back to canonical execution. These shortcuts rely on the documented requirement that every valid value fits the child precision.

Shares the canonical comparison assertion and decimal fixtures, removes duplicate scalar precision coverage, and reduces the new decimal test cases from 193 to 99. Keeps the four paired benchmark cases for in-range comparisons and the out-of-range shortcut.

Historical local benchmark results

These retained measurements predate this style pass and rebase. They do not measure the current head.

cargo bench -p vortex-array --bench widen_decimal, Apple M4, medians. The numbers were measured at the top of this stack before the split, so they cover the whole stack, not this layer alone. The baseline is the canonical input, and the candidate is the same values as a Widen (child precision 5 under precision 10, 9 under 18, and 18 under 38). CodSpeed on this PR measures the layer itself.

bench p10 canonical p10 Widen p18 canonical p18 Widen p38 canonical p38 Widen
compare_constant 4.166 µs 5.041 µs 4.124 µs 5.041 µs 10.7 µs 4.207 µs
compare_constant_out_of_range 3.791 µs 374.5 ns 3.79 µs 374.5 ns 9.874 µs 397.9 ns

API Changes

None.

Validation

Source review and downstream rebase range-diffs. No local tests, builds, formatting, linting, or benchmarks were run, per repository instructions.

@codspeed

codspeed Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 12.91%

1 warning

⚠️ Walltime results may be unreliable

Some benchmarks used the Walltime instrument outside CodSpeed Macro Runners, which can make the results inconsistent.

Learn more about Macro Runners

See affected benchmarks

❌ 4 regressed benchmarks
✅ 1878 untouched benchmarks
⏩ 469 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ CPU Simulation bench_compare_varbin[(50000, 2)] 133.6 µs 162.3 µs -17.71%
❌ CPU Simulation bench_compare_sliced_dict_varbinview[(1000, 10000)] 79.7 µs 90.5 µs -11.94%
❌ CPU Simulation bench_compare_sliced_dict_varbinview[(9999, 10000)] 206.6 µs 233.4 µs -11.47%
❌ Walltime add_constant_shapes_neon[(32768, PerRowNullableConstant)] 14.9 µs 16.6 µs -10.32%

Tip

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


Comparing ct/decimal-widen-compare (b991aa6) with develop (d166762)

Open in CodSpeed

Footnotes

  1. 469 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. ↩

@connortsui20 connortsui20 added the changelog/performance A performance improvement label Oct 29, 2026
@connortsui20 connortsui20 mentioned this pull request Oct 30, 2026
20 of 34 tasks
@connortsui20
connortsui20 force-pushed the ct/decimal-widen-compare branch from 3f13e37 to e4a453e Compare October 30, 2026 17:50
@connortsui20
connortsui20 removed this pull request from stack #11007 October 30, 2026 18:02
@connortsui20
connortsui20 added this pull request to stack #11058 October 30, 2026 18:04
@connortsui20
connortsui20 force-pushed the ct/decimal-widen-compare branch 2 times, most recently from 825399a to 956796c Compare November 2, 2026 13:50
@connortsui20
connortsui20 force-pushed the ct/decimal-widen-compare branch from 956796c to 26f34a6 Compare November 2, 2026 16:57
@connortsui20
connortsui20 removed this pull request from stack #11058 November 2, 2026 16:59
@connortsui20
connortsui20 added this pull request to stack #11104 November 2, 2026 16:59
@connortsui20
connortsui20 force-pushed the ct/decimal-widen-compare branch 4 times, most recently from c626cfa to 86478c0 Compare November 2, 2026 19:52
@connortsui20
connortsui20 force-pushed the ct/decimal-widen-compare branch 2 times, most recently from 6742cd1 to 9b61103 Compare November 2, 2026 21:29
@connortsui20 connortsui20 changed the title Push decimal comparisons, between and fills through Widen Push decimal comparisons, between, and fills through Widen Nov 3, 2026
@connortsui20
connortsui20 force-pushed the ct/decimal-widen-compare branch 2 times, most recently from 5b902e2 to 4bd352b Compare November 3, 2026 13:36
Base automatically changed from ct/decimal-widen to develop November 3, 2026 14:22
connortsui20 added a commit that referenced this pull request Nov 3, 2026
Tracking Issue: #10701

Based on `develop`, including the writer work previously in #11002.
#11003 and #10994 build on it. No release gate.

## Summary

Canonical decimal width follows precision, so small values under
`Decimal(38, 2)` still occupy 16 bytes each. A decimal `Widen` keeps
those values in a lower-precision child while retaining the column's
logical dtype.

## Changes

The child precision must hold every valid value and have a narrower
canonical width. Execution restores the logical width. Until the later
kernels land, operations use that canonical fallback.

Serialization writes a concrete decimal leaf through the existing
`vortex.decimal` wire form without executing values or validity. Integer
`Widen` arrays, non-decimal leaves, and nonserializable validity require
explicit preparation by the caller. Its metadata stores the width, while
the containing format supplies the parent precision. This keeps an `i32`
child at four bytes per value, including IPC and writes without a
compressor. A serialized-byte regression test checks that size
independently of the eventual read encoding.

<details>
<summary>Historical local benchmark results</summary>

These retained measurements predate this style pass and rebase. They do
not measure the current head.

`cargo bench -p vortex-array --bench widen_decimal`, Apple M4, medians.
The numbers were measured at the top of this stack before the split, so
they cover the whole stack, not this layer alone. The baseline is the
canonical input, and the candidate is the same values as a `Widen`
(child precision 5 under precision 10, 9 under 18, and 18 under 38).
CodSpeed on this PR measures the layer itself.

| bench | p10 canonical | p10 Widen | p18 canonical | p18 Widen | p38
canonical | p38 Widen |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| `filter` | 22.16 µs | 5.083 µs | 13.66 µs | 4.916 µs | 10.58 µs |
24.74 µs |
| `execute` | 10.59 ns | 2.832 µs | 10.26 ns | 2.926 µs | 10.34 ns |
9.895 µs |

`execute` on a `Widen` is the widening copy, which the canonical input
does not need. The canonical `filter` medians at p10 and p18 were
bimodal across runs, and `filter` at p38 is slower on the `Widen`.

</details>

## API Changes

`WidenArray::try_new` accepts decimal children. Adds
`WidenArray::encode_decimal` and registers a writer-only `WidenPlugin`,
whose only write ID is `vortex.decimal`.

## Validation

Source review and rebase range-diffs only. No local tests, builds,
linting, or documentation checks were run. A commit hook flagged one
formatting wrap, corrected manually without rerunning the check.

---------

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
@connortsui20
connortsui20 force-pushed the ct/decimal-widen-compare branch from 4bd352b to 758a37d Compare November 3, 2026 14:22
@connortsui20
connortsui20 marked this pull request as ready for review November 3, 2026 14:41
@connortsui20
connortsui20 force-pushed the ct/decimal-widen-compare branch 2 times, most recently from 93dee65 to ed42055 Compare November 3, 2026 15:11
Comment thread vortex-array/benches/widen_decimal.rs Outdated
Comment on lines +278 to +298
#[divan::bench(args = wide_cases())]
fn compare_constant(bencher: Bencher, case: Case) {
let values = case.values();
bench_case(
bencher,
|| (values.array(case), case.constant(0)),
|(array, constant), ctx| execute_canonical(array.binary(constant, Operator::Lt), ctx),
);
}

#[divan::bench(args = wide_cases())]
fn compare_constant_out_of_range(bencher: Bencher, case: Case) {
let values = case.values();
bench_case(
bencher,
|| {
(
values.array(case),
case.constant(10i64.pow(u32::from(case.child_precision))),
)
},

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.

do we actually need more benchmarks. You could unit test the pushdown?

@joseph-isaacs joseph-isaacs 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.

Code looks good.

I think we need less benchmark, tighter test.

Maybe assert of the reduction of a compare + widen

Compare decimal children at a common precision, narrow constants that fit,
and answer out-of-range comparisons without widening the child values.
Share the cast and execution paths between integer and decimal kernels.
Decimal fills outside the child precision use canonical execution.

Assert narrow comparison operand dtypes and concrete out-of-range results
through the registered comparison kernel, including validity and null
constants. Compare logical results with canonical decimal operations.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
@connortsui20
connortsui20 force-pushed the ct/decimal-widen-compare branch from ed42055 to b991aa6 Compare November 3, 2026 16:31
@connortsui20

Copy link
Copy Markdown
Member Author

I removed the benchmarks

@connortsui20
connortsui20 enabled stacked auto-merge (squash) November 3, 2026 16:31
@connortsui20
connortsui20 merged commit 41dec20 into develop Nov 3, 2026
91 of 93 checks passed
@connortsui20
connortsui20 deleted the ct/decimal-widen-compare branch November 3, 2026 16:46
connortsui20 added a commit that referenced this pull request Nov 3, 2026
Tracking Issue: #10701

Stacked on #11003. #11005 builds on it. No release gate.

## Summary

Decimal widening preserves order, equality and validity, so ordered
aggregates can use the child instead of copying it first. Sum also needs
the logical result precision: using the child's sum dtype could overflow
a result that should fit.

## Changes

Ordered aggregates widen their child partials, and MinMax reuses exact
cached child extrema before scanning. Decimal `Sum` runs in the logical
sum dtype only when the legacy `Stat::Sum` path combines independent
batch totals. `SumV2` and `Sum` without `skip_nans` keep the fallback
because their running sum can cancel a batch total that would overflow
on its own. Tests compare running partials against fresh canonical
batches so cached statistics cannot hide kernel work.

<details>
<summary>Historical local benchmark results</summary>

These retained measurements predate this style pass and rebase. They do
not measure the current head.

`cargo bench -p vortex-array --bench widen_decimal`, Apple M4, medians.
The numbers were measured at the top of this stack before the split, so
they cover the whole stack, not this layer alone. The baseline is the
canonical input, and the candidate is the same values as a `Widen`
(child precision 5 under precision 10, 9 under 18, and 18 under 38).
CodSpeed on this PR measures the layer itself.

| bench | p10 canonical | p10 Widen | p18 canonical | p18 Widen | p38
canonical | p38 Widen |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| `min_max` | 14.93 µs | 15.12 µs | 14.62 µs | 15.12 µs | 21.58 µs |
14.95 µs |

`sum` medians are within 8% of the canonical input at every precision.

</details>

## API Changes

None.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/performance A performance improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants