Skip to content

Run Widen arithmetic, cast, fill null, zip and between on the values - #10891

Merged
connortsui20 merged 7 commits into
developfrom
ct/narrow-arithmetic-kernels
Oct 28, 2026
Merged

connortsui20 merged 7 commits into
developfrom
ct/narrow-arithmetic-kernels

Conversation

@connortsui20

@connortsui20 connortsui20 commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Summary

This finishes the integer kernels for Widen. Checked arithmetic on two Widen arrays, or on a Widen and a constant, now runs at the smallest integer type that holds both operands and the result, instead of at the logical type. Cast, fill null, zip and between also run on the values.

Based on #10890.

Changes

The arithmetic kernel reads the extrema of each operand and computes an interval that holds every result of add, subtract, multiply or divide. It runs the operation at the smallest type of the logical signedness that holds the operands and that interval. When only the logical type holds it, the operation runs at the logical type, so an overflow or a division by zero fails with the same error as canonical execution. Any other operand declines, so the default path widens a wide or lazy operand once. A brute-force test checks the interval against every result over small ranges.

Cast now also sends a float target, and an integer target of the other signedness, to the values. Fill null widens the values only as far as the fill value needs. Zip runs on the values when the Widen is the if_true branch and the if_false branch is a Widen or a constant. ZipExecuteAdaptor dispatches only on if_true, so a Widen in if_false takes the default path, as for every other encoding. Between runs on the values when both bounds are constants inside the range of the values, and as two compares on the Widen when the bounds are other constants or Widen arrays.

Open question: since #10864, cast sends narrowing casts to the values, and this PR also sends sign-changing and float casts there. A failing cast fails on the same rows as canonical execution, but its message names the storage type, for example i8 to u8 instead of i64 to u32. Is that acceptable, or should those casts decline?

For review, read the commits one at a time.

@codspeed

codspeed Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Merging this PR will regress 2 benchmarks

3 warnings

⚠️ 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

⚠️ 2 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

⚡ 8 improved benchmarks
❌ 2 regressed benchmarks
✅ 2260 untouched benchmarks
⏩ 370 skipped benchmarks1
🗄️ 30 archived benchmarks run2

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Walltime threshold_neon[64] 2.6 µs 3.1 µs -16.68%
❌ Walltime threshold_neon[80] 3.5 µs 3.9 µs -11.06%
⚡ CPU Simulation between_in_range[widen] 468.1 µs 137.1 µs ×3.4
⚡ CPU Simulation cast_f64[widen] 725.3 µs 349.7 µs ×2.1
⚡ CPU Simulation add_widen[widen] 1,063.1 µs 602.8 µs +76.37%
⚡ CPU Simulation zip[widen] 1,170.5 µs 705.7 µs +65.85%
⚡ CPU Simulation fill_null_out_of_range[widen] 1.7 ms 1.2 ms +34.11%
⚡ CPU Simulation add_constant[widen] 695.4 µs 537.7 µs +29.32%
⚡ CPU Simulation filter_powerlaw_by_random[10000] 808.1 ns 694.8 ns +16.31%
⚡ Walltime lt_i64_nullable_avx512 8.2 µs 7.3 µs +11.68%

Tip

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


Comparing ct/narrow-arithmetic-kernels (419b196) with develop (37031b1)

Open in CodSpeed

Footnotes

  1. 370 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. 30 benchmarks were run, but are now archived. If they were deleted in another branch, consider rebasing to remove them from the report. Instead if they were added back, click here to restore them. ↩

@connortsui20
connortsui20 force-pushed the ct/narrow-arithmetic-kernels branch 4 times, most recently from 4c17aca to cfa0805 Compare October 27, 2026 19:29
Comment on lines +51 to +60
let cast_ptype = match (values.is_signed_int(), target.is_signed_int()) {
// `validate_dtypes` keeps the values narrower than 64 bits.
(false, true) => match values {
PType::U8 => PType::I16,
PType::U16 => PType::I32,
_ => PType::I64,
},
(true, false) => values.to_unsigned(),
_ => values,
};

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is basically trying to be clever, where we try to cast into a smaller bit width than we need to while remaining under Narrow.

Is this fine? We save 2x or more memory by doing this, but it could potentially cause even more work in the future, assuming some other operation cannot be pushed down into Narrow.

}

/// Returns `array` executed to its canonical form.
fn canonical(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<ArrayRef> {

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.

this looks very weird

@robert3005 robert3005 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.

Apart from the some weird test code this looks fine

@connortsui20
connortsui20 force-pushed the ct/narrow-arithmetic-kernels branch from dffc937 to 6b937fd Compare October 28, 2026 15:39
@connortsui20 connortsui20 changed the title Run Narrow arithmetic, cast, fill null, zip and between on the values Run Widen arithmetic, cast, fill null, zip and between on the values Oct 28, 2026
@connortsui20
connortsui20 force-pushed the ct/narrow-arithmetic-kernels branch 3 times, most recently from f114a89 to 16047c4 Compare October 28, 2026 16:42
Base automatically changed from ct/narrow-aggregate-kernels to develop October 28, 2026 17:03
Checked add, subtract, multiply and divide of a Widen and another Widen
or a constant now run at the smallest integer type that holds both
operands and the result interval. When only the logical type holds the
interval, the operation runs at the logical type, so overflow and
division by zero fail exactly as canonical execution does. Any other
operand declines.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
The cast rule now sends a float target to the values, and handles an
integer target of the other signedness through a storage type of the
target signedness. Fill null widens the values to the smallest type that
holds the fill value. Zip runs on the values when the other branch is a
Widen or a constant, with the Widen in either branch, through a
ZipKernel and a second execute-parent kernel for the if_false branch.
Between runs on the values when both bounds are constants inside their
range, and as two compares of Widen when the bounds are constants or
Widen arrays.

The compare, zip and between kernels share the helpers for the wider
value type and the constant cast in widen/compute/mod.rs, and the test
modules share stored_ptype and canonical.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
…ernels

Share one helper that casts values to a type and keeps their nullability,
move the width choice of the arithmetic kernel into arithmetic_ptype, and
give integer_storage_type and storage_ptype names that say what they
return. Shorten the docs to the reasons.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Rename the shared helpers and the arithmetic helpers so that each name
says what it returns, move constant_fits_values next to the other shared
helpers now that between also calls it, and document where the
arithmetic kernel relies on min and max statistics.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
ZipExecuteAdaptor dispatches only on the if_true branch, so the second
kernel needed its own execute-parent impl and a branch enum. A Widen in
the if_false branch now takes the default path, as every other encoding
does.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Compare the complete error message without its captured backtrace. The
canonical and Widen paths have different call stacks when CI enables
backtraces, even when they return the same error.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
@connortsui20
connortsui20 force-pushed the ct/narrow-arithmetic-kernels branch from 16047c4 to 419b196 Compare October 28, 2026 17:03
@connortsui20
connortsui20 merged commit bd93ae7 into develop Oct 28, 2026
80 of 81 checks passed
@connortsui20
connortsui20 deleted the ct/narrow-arithmetic-kernels branch October 28, 2026 17:12
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