Skip to content

RowFn binary numeric ScalarFn rewrite - #9320

Open
connortsui20 wants to merge 2 commits into
ct/row-fn-apifrom
ct/row-fn-numeric
Open

RowFn binary numeric ScalarFn rewrite#9320
connortsui20 wants to merge 2 commits into
ct/row-fn-apifrom
ct/row-fn-numeric

Conversation

@connortsui20

@connortsui20 connortsui20 commented Aug 10, 2026

Copy link
Copy Markdown
Member

Tracking Issue: #9130

Moves primitive numeric arithmetic and comparisons to RowFn. Selected measured comparison cases retain their columnar paths.

TODO

@connortsui20 connortsui20 added the changelog/performance A performance improvement label Aug 10, 2026
Route primitive numeric kernels through RowFn while retaining Binary as the public scalar function identity.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
@codspeed-hq

codspeed-hq Bot commented Aug 10, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 16.05%

⚡ 1 improved benchmark
❌ 60 regressed benchmarks
✅ 1899 untouched benchmarks
🆕 1 new benchmark
⏩ 85 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation case_when_nary_10_conditions[1000] 421.3 µs 606.1 µs -30.49%
Simulation compact_sliced[(16384, 10)] 1.4 ms 1.9 ms -26.52%
Simulation compact_sliced[(4096, 10)] 385.6 µs 515.5 µs -25.19%
Simulation baseline_lt[4, 1024] 98 µs 129.6 µs -24.39%
Simulation case_when_nary_equality_lookup[1000] 323.9 µs 421.3 µs -23.13%
Simulation case_when_nary_10_conditions[10000] 765.8 µs 991 µs -22.72%
Simulation multipolygon_random_nulls 951.3 µs 1,223.1 µs -22.22%
Simulation eq_i64_constant 372.8 µs 478.6 µs -22.12%
Simulation baseline_eq[4, 1024] 97.4 µs 125 µs -22.06%
Simulation baseline_lt[16, 1024] 99.4 µs 127.1 µs -21.81%
Simulation baseline_eq[16, 1024] 99.2 µs 126.4 µs -21.56%
Simulation case_when_nary_3_conditions[1000] 236.6 µs 299.1 µs -20.92%
Simulation case_when_nary_early_dominant[1000] 235 µs 294.5 µs -20.23%
Simulation bench_compare_sliced_dict_primitive[(1000, 10000)] 130.1 µs 160.4 µs -18.92%
Simulation case_when_nary_equality_lookup[10000] 527.7 µs 649 µs -18.69%
Simulation case_when_all_false[1000] 123.8 µs 151.8 µs -18.45%
Simulation multipolygon_non_nullable 1.2 ms 1.5 ms -18.26%
Simulation bench_compare_sliced_dict_primitive[(2000, 10000)] 143.8 µs 175.4 µs -18.02%
Simulation bench_compare_sliced_dict_primitive[(2500, 10000)] 151 µs 184.1 µs -17.99%
Simulation multipolygon_mixed_validity 1.2 ms 1.5 ms -17.99%
... ... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

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


Comparing ct/row-fn-numeric (32ad0bf) with ct/row-fn-api (84837ad)

Open in CodSpeed

Footnotes

  1. 85 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 changed the title Execute primitive numeric operators with RowFn Execute primitive binary operators with RowFn Aug 10, 2026

connortsui20 commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

Local wall-time results do not reproduce the broad CodSpeed simulation regressions. Ratios below are candidate time / base time, so lower is better.

For example, locally (on my machine) the comparison commit improves the eq_i64_constant case to 0.817x locally, whereas CodSpeed reports a 1.221x regression. There are many more benchmarks that are wildly different on my local machine.

Measurement types
  • Isolated: A directly affected benchmark run by itself in a fresh process. Each result is the median of six base/candidate process pairs. Each revision ran first in three pairs. These are the primary performance measurements.
  • Control: A benchmark that does not execute the code changed by the commit. Controls came from eight full-matrix runs with alternating revision order. They detect unrelated process, allocator, cache, and machine movement.
  • Unstable control: An unchanged control with a wide range across process medians. Its aggregate ratio is not evidence for or against the change.
Primitive comparison results

Base is the numeric-only commit edbbbcd07. Candidate is the comparison commit 37eb039e2.

Benchmark Base Candidate Ratio Type
compare_int 27.395 µs 27.360 µs 0.999× isolated
compare_int_nullable 27.555 µs 27.560 µs 1.000× isolated
compare_int_constant 23.170 µs 23.200 µs 1.001× isolated
compare_int_constant_lhs 23.155 µs 23.185 µs 1.001× isolated
compare_int_eq 21.685 µs 19.310 µs 0.890× isolated
compare_i32 19.020 µs 7.389 µs 0.388× isolated
compare_i32_constant 18.990 µs 7.784 µs 0.410× isolated
compare_f32 33.125 µs 14.360 µs 0.434× isolated
compare_f32_eq 19.050 µs 7.214 µs 0.379× isolated
compare_float 49.590 µs 49.620 µs 1.001× isolated
compare_float_eq 21.805 µs 19.390 µs 0.889× isolated
compare_u64 27.375 µs 23.755 µs 0.868× isolated
compare_u64_constant 23.190 µs 23.210 µs 1.001× isolated
compare_u64_eq 21.780 µs 19.360 µs 0.889× isolated
compare_u8 39.045 µs 3.034 µs 0.078× isolated
compare_u8_constant 31.320 µs 3.374 µs 0.108× isolated

Ten cases are at or below 0.890×. The remaining six are between 0.999× and 1.001×.

Comparison controls

Base is edbbbcd07. Candidate is 37eb039e2.

Benchmark Base Candidate Ratio Type
compare_bool 1.504 µs 1.499 µs 0.997× control
compare_bool_constant 0.740 µs 0.740 µs 1.000× control
compare_bool_nullable 1.719 µs 1.729 µs 1.006× control
compare_decimal 37.090 µs 37.115 µs 1.001× control
compare_string_constant 36.145 µs 35.800 µs 0.990× control
compare_string_eq 32.250 µs 32.325 µs 1.002× control
compare_string_lt 44.600 µs 44.565 µs 0.999× control
compare_struct_eq 181.700 µs 180.550 µs 0.994× unstable control
compare_struct_lt 237.800 µs 238.500 µs 1.003× unstable control

The process medians ranged from 180.0 to 195.3 µs for the base compare_struct_eq control and from 179.8 to 194.6 µs for the candidate. For compare_struct_lt, the ranges were 194.6 to 252.9 µs and 180.0 to 253.5 µs.

Primitive numeric results

Base is the RowFn API commit 84837ad36, which does not use RowFn for primitive numeric arithmetic. Candidate is the numeric-only commit edbbbcd07, which does.

Benchmark Base Candidate Ratio Type
add_i32_nonnull 4.479 µs 4.659 µs 1.040× isolated
add_i64_constant 8.414 µs 9.254 µs 1.100× isolated
add_i64_nonnull 9.159 µs 9.389 µs 1.025× isolated
add_i64_nullable 10.275 µs 10.275 µs 1.000× isolated
add_u32_nonnull 4.019 µs 4.194 µs 1.044× isolated
div_i64_nonnull 44.800 µs 45.050 µs 1.006× isolated
div_i64_nullable 37.120 µs 41.165 µs 1.109× isolated
mul_i8_nonnull 6.179 µs 6.364 µs 1.030× isolated
mul_i16_nonnull 4.159 µs 4.339 µs 1.043× isolated
mul_i32_constant 26.440 µs 18.915 µs 0.715× isolated
mul_i32_nonnull 26.420 µs 26.640 µs 1.008× isolated
mul_i32_nullable 27.375 µs 27.450 µs 1.003× isolated
mul_i64_nonnull 23.175 µs 23.410 µs 1.010× isolated
mul_u8_nonnull 3.339 µs 3.539 µs 1.060× isolated
mul_u16_nonnull 2.614 µs 2.404 µs 0.920× isolated
mul_u32_nonnull 7.014 µs 7.184 µs 1.024× isolated
mul_u64_nonnull 19.160 µs 19.390 µs 1.012× isolated
sub_i64_constant 8.259 µs 9.164 µs 1.110× isolated

The numeric port is mixed. Two cases are below 0.921×, four are between 1.000× and 1.008×, and twelve are between 1.010× and 1.110×.

Numeric controls

Base is 84837ad36. Candidate is edbbbcd07.

Benchmark Base Candidate Ratio Type
add_decimal_i128_nullable 124.500 µs 124.800 µs 1.002× control
add_decimal_i64_nonnull 33.030 µs 33.140 µs 1.003× unstable control
and_bool_nullable 2.194 µs 2.244 µs 1.023× unstable control
div_decimal_i128_nullable 283.800 µs 283.700 µs 1.000× control
div_decimal_i64_nonnull 51.490 µs 51.470 µs 1.000× control
eq_i64_constant 12.040 µs 12.020 µs 0.998× control
lt_i64_nullable 16.705 µs 16.680 µs 0.999× control
mul_decimal_i128_nullable 82.780 µs 82.840 µs 1.001× control
mul_decimal_i64_nonnull 10.865 µs 10.865 µs 1.000× control
or_bool_constant 0.880 µs 0.890 µs 1.011× control

The base and candidate ranges for add_decimal_i64_nonnull were 31.590 to 34.470 µs and 31.660 to 34.590 µs. The ranges for and_bool_nullable were 2.179 to 2.669 µs and 2.209 to 2.339 µs.

Cross-benchmark sensitivity

The eight-run full matrix gave compare_int_nullable a 1.476× aggregate ratio. Running that benchmark alone gave 1.000×. This isolates the apparent regression to cross-benchmark process state, not the comparison kernel.

This is also why the tables separate isolated measurements from controls and unstable controls.

Method
  • CPU: AMD Ryzen 9 7950X, pinned to logical CPU 2.
  • Toolchain: Rust 1.91.0 with LLVM 21.1.2.
  • Build: default target, bench profile optimization level 3, 16 codegen units.
  • Timer: TSC, 100 samples.
  • Full matrix: 53 cases, eight fresh-process runs, alternating revision order, 500 ms per case.
  • Isolated cases: six fresh-process pairs, with each revision first three times, 500 ms per case.
  • Exact CodSpeed cases: eight fresh-process pairs, one second per case.
  • Each reported time is the median of the per-process Divan medians.
  • The same benchmark source was used for each compared revision.

These are local wall-time measurements. They are not CodSpeed CPU-simulation measurements and do not explain CodSpeed cache or instruction-cost changes.

Observations

There are only a few numeric arithmetic regressions of at most 10%, and they all are related to 64-bit operations being harder for the compiler to optimize. If I can't find a way to undo this regression via the RowFn API, then we can just special case on those and use handwritten kernels.

But in general, everything else is at parity or obviously better.

Use RowFn for primitive comparisons while retaining fused x86 bit-packing for the measured wide ordered cases where LLVM generates faster code.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
@connortsui20
connortsui20 marked this pull request as ready for review August 10, 2026 02:59
@connortsui20 connortsui20 changed the title Execute primitive binary operators with RowFn RowFn Binary Numeric Implementations Aug 10, 2026
@connortsui20 connortsui20 changed the title RowFn Binary Numeric Implementations RowFn binary numeric ScalarFn rewrite Aug 10, 2026
@joseph-isaacs

Copy link
Copy Markdown
Contributor

To help with the regressions, we could have support a mode that gives you constant size blocks of data instead of just row. But that can come later!

@connortsui20

connortsui20 commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

@joseph-isaacs I already have batch execution built in to the machinery driving the RowFn framework, and also if we did that it would make it harder to define constant handling and null propagation semantics.

I am pretty certain that the regressions come from 1) initialization costs that might not need to be paid (this is why we have the UninitElementSink but it might be a bit wrong rn) and 2) cache line boundaries (something fit in 64-byte cache line but not anymore with all these generics) which are unfixable and have to have special casing around the RowFn.

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