Skip to content

[DO NOT MERGE] Measure the primitive comparison migration without the fused RowFn executor - #11220

Closed
connortsui20 wants to merge 3 commits into
developfrom
ct/row-fn-cleanup/primitive-compare-on-develop
Closed

connortsui20 wants to merge 3 commits into
developfrom
ct/row-fn-cleanup/primitive-compare-on-develop

Conversation

@connortsui20

Copy link
Copy Markdown
Member

Benchmark experiment for #10675 and #10676. Do not merge.

This branch applies the two #10676 commits directly on develop at bc58587293, without #10675. CodSpeed then measures the primitive comparison migration on the RowFn executor that develop already has. Its comparison results can be read against the #10676 report, which measures the same two commits on top of #10675, so the difference between the two reports is the contribution of #10675.

Close this PR once CodSpeed reports.

Keep the comparison type and operator dispatch, constant orientation, borrowed operand dtypes, and comparison coverage in this layer. Shared decoding, validity planning, and packed output belong to the framework below. The required input classification runs before decoding, and this layer adds no one-pass decoder hook or eager validity conjunction.

Signed-off-by: Connor Tsui <[email protected]>
total_cmp sorts a NaN with the sign bit set below -inf, so NaN is not always the largest value.

Signed-off-by: Connor Tsui <[email protected]>
@connortsui20
connortsui20 marked this pull request as ready for review November 4, 2026 15:18
@connortsui20 connortsui20 changed the title [Do not merge] Measure the primitive comparison migration without the fused RowFn executor [DO NOT MERGE] Measure the primitive comparison migration without the fused RowFn executor Nov 4, 2026
@codspeed

codspeed Bot commented Nov 4, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 8.2%

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

⚡ 3 improved benchmarks
❌ 15 regressed benchmarks
✅ 1747 untouched benchmarks
⏩ 130 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ CPU Simulation int_gt[16] 97.9 µs 115.9 µs -15.59%
❌ CPU Simulation int_gt[1024] 102.4 µs 119.7 µs -14.47%
❌ Walltime compare_u8_avx512 1.8 µs 2.1 µs -14.38%
❌ Walltime compare_u8_neon 2 µs 2.3 µs -13.3%
❌ CPU Simulation encode_primitives[f32, (2000, 2)] 183.8 µs 210.7 µs -12.77%
❌ CPU Simulation or_chain[16] 364.5 µs 415.5 µs -12.28%
❌ CPU Simulation float_gt[16] 244.5 µs 278.6 µs -12.23%
❌ CPU Simulation float_gt[1024] 264.9 µs 301.2 µs -12.06%
❌ CPU Simulation or_chain[1024] 371.8 µs 421.9 µs -11.88%
❌ CPU Simulation i64_lt 81.1 µs 91.9 µs -11.74%
❌ CPU Simulation is_not_null_pred[16] 200.6 µs 226.9 µs -11.61%
❌ CPU Simulation bench_compare_sliced_dict_primitive[(1000, 10000)] 98.3 µs 111.1 µs -11.48%
❌ CPU Simulation is_not_null_pred[1024] 206.3 µs 230.6 µs -10.54%
❌ Walltime compare_f32_avx512 2.7 µs 3 µs -10.37%
❌ CPU Simulation timestamp_gte 121.3 µs 135.3 µs -10.34%
⚡ Walltime compare_float_avx2 5.2 µs 4.4 µs +18.43%
⚡ Walltime compare_u64_avx2 4.1 µs 3.5 µs +15.66%
⚡ CPU Simulation random_i8[0.01] 119.3 µs 105.6 µs +13.03%

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-cleanup/primitive-compare-on-develop (009df1d) with develop (c9a3fbe)

Open in CodSpeed

Footnotes

  1. 130 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

Copy link
Copy Markdown
Member Author

CodSpeed has reported, so this experiment is done. The result is summarized in the #10675 description.

connortsui20 added a commit that referenced this pull request Nov 5, 2026
Tracking Issue: #9128

## Summary

Combines planning and execution for eligible RowFn batches in one type
dispatch. This is the shared framework layer for #10676, which contains
the primitive comparison migration. It builds on #10899, which separates
batch inspection from planning and adds the regression cases this PR
must keep passing.

## Changes

- Decodes a direct Boolean batch straight from the borrowed inputs,
after `BatchProbe` has classified all of them, so a later null literal
still skips an earlier fallible expression or device transfer. Fallible
decoders keep valid-row execution.
- Finishes all-null, all-constant, null-free, and dense batches in the
planning visit. Every other batch comes back as a `PartiallyValidBatch`,
the only input that dense retry and valid-row execution accept, so every
caller runs the fast paths first.
- Constructs Boolean output with its validity in the shared executor.
Dense execution masks any output that does not carry that validity, and
a validity of the wrong length returns an error. Primitive-specific CPU
and lane-width packing policies stay out of this layer.
- In builds that enable AVX-512F and AVX-512BW at compile time,
`collect_bool_words_multiversioned` uses the inline packing loop instead
of the `#[target_feature]` copy, so its inlining no longer depends on
codegen-unit placement.

## Performance

This PR lowers the fixed cost of each RowFn call, so CodSpeed shows no
gain for it alone. The gain shows up in #10676, which moves primitive
comparison onto the RowFn executor. To isolate this PR, #11220 applied
the two #10676 commits directly on `develop`. Without this PR,
comparisons on small arrays are 10% to 16% slower than `develop` in CPU
simulation, including the zone map pruning benchmarks, `i64_lt`, and
`timestamp_gte`. The added time barely changes between 16 and 1,024
zones (18.0 µs and 17.3 µs for `int_gt`), so it is paid per call, not
per row. On top of this PR, #10676 shows none of these regressions, and
its walltime comparison gains are larger: `compare_u64_avx2` improves by
31% instead of 16%, and seven more comparison benchmarks improve by 12%
to 18%.

In the AVX-512 build, the RowFn changes moved the deferred Boolean
kernel's AVX-512 packing loop out of its caller's codegen unit, so LLVM
could no longer inline it. CodSpeed reported the two partially valid
`deferred_bool_avx512` cases as 42% and 16% slower. With the last
commit, CodSpeed no longer flags either case. Builds that detect AVX-512
at runtime compile the same code as before. #11220 also shows
`compare_u8_avx512` and `compare_f32_avx512` as 14% and 10% slower
without this PR, which matches the same packing problem, but that
build's assembly wasn't inspected.

CodSpeed on the current head flags three varbin comparison cases and
`random_i8[0.01]` as 12% to 18% slower, and one `take_filter` case as
13% faster. None of them runs a RowFn. #10676 reverses the varbin and
`take_filter` changes without touching their code, so they follow code
placement. `random_i8[0.01]` isn't a stable signal either: its `develop`
baseline moved from 99.6 µs to 119.3 µs between `bc58587` and `c9a3fbe`,
and #11220 moved it 13% the other way.

<details>
<summary>CodSpeed results for #10676 with and without this PR</summary>

The first column is #11220, which compares the #10676 commits on
`develop` with `develop` (`c9a3fbe`). The second column is the #10676
report, which compares the same commits on this PR with this PR
(`b0a64dd`). Against `develop` (`bc58587`), this PR flags none of these
benchmarks. CodSpeed marks the walltime runners as unreliable, and the
two columns come from different runs.

| Benchmark | Mode | Without this PR | With this PR |
| --- | --- | ---: | ---: |
| `int_gt[16]` | CPU simulation | 15.6% slower | not flagged |
| `int_gt[1024]` | CPU simulation | 14.5% slower | not flagged |
| `float_gt[16]` | CPU simulation | 12.2% slower | not flagged |
| `float_gt[1024]` | CPU simulation | 12.1% slower | not flagged |
| `or_chain[16]` | CPU simulation | 12.3% slower | not flagged |
| `or_chain[1024]` | CPU simulation | 11.9% slower | not flagged |
| `is_not_null_pred[16]` | CPU simulation | 11.6% slower | not flagged |
| `is_not_null_pred[1024]` | CPU simulation | 10.5% slower | not flagged
|
| `i64_lt` | CPU simulation | 11.7% slower | not flagged |
| `timestamp_gte` | CPU simulation | 10.3% slower | not flagged |
| `bench_compare_sliced_dict_primitive[(1000, 10000)]` | CPU simulation
| 11.5% slower | not flagged |
| `encode_primitives[f32, (2000, 2)]` | CPU simulation | 12.8% slower |
not flagged |
| `compare_u8_avx512` | Walltime | 14.4% slower | not flagged |
| `compare_u8_neon` | Walltime | 13.3% slower | not flagged |
| `compare_f32_avx512` | Walltime | 10.4% slower | not flagged |
| `compare_float_avx2` | Walltime | 18.4% faster | 28.2% faster |
| `compare_u64_avx2` | Walltime | 15.7% faster | 30.8% faster |
| `lt_i64_nullable_avx2` | Walltime | not flagged | 18.4% faster |
| `compare_int_constant_neon` | Walltime | not flagged | 16.2% faster |
| `compare_int_eq_avx2` | Walltime | not flagged | 15.4% faster |
| `compare_f32_avx2` | Walltime | not flagged | 15.4% faster |
| `compare_int_constant_avx2` | Walltime | not flagged | 15.3% faster |
| `compare_int_avx2` | Walltime | not flagged | 13.5% faster |
| `compare_int_nullable_avx2` | Walltime | not flagged | 12.5% faster |

</details>

<details>
<summary>AVX-512 investigation on EC2</summary>

C7i metal (Xeon Platinum 8488C) with the CodSpeed AVX-512 build flags,
the bench profile, one isolated CPU, and 21 paired blocks of 1,000
samples. Ratios are the median paired ratio to `develop` (`8c87c4c457`).
Below 1 is faster. An A/A comparison of two identical binaries flags
none of the 100 cases.

| `deferred_bool_avx512` case | `develop` | Before fix | With fix |
| --- | ---: | ---: | ---: |
| `ConstantLhs, PartialAccepted` | 32.1 µs | 1.354 | 1.025 |
| `Columns, PartialAccepted` | 37.7 µs | 1.159 | 1.018 |

Before the fix, the kernel executed 15% more instructions per row. LLVM
inline remarks show `collect_bool_words_avx512` inlined on `develop` and
"definition is unavailable" on this branch.

Across all 100 `#[cpu_features]` cases, the fix also returns
`compare_string_lt` to the `develop` speed. It makes two
`intersect_by_rank` cases and one `starts_with` case 3.5% to 5.6%
slower, but those execute the same instructions as before, so the
difference is code placement. The AVX2 build is unchanged.

</details>

<details>
<summary>Historical EC2 framework measurements</summary>

These numbers belong to an older framework replay and do not establish
the performance of this revision. Ratios are the median of 21 paired
candidate/baseline elapsed-time ratios. Below 1 is faster. This compares
the framework alone, F `94be28140d5bd7e6b654af5e462253add2e49044`, with
D `0291778db36809f77ce0219bf9850506d3cba3b6`.

| Workload | C7g NEON | C6a AVX2 | C7i AVX2 build | C7i AVX-512 build |
| --- | ---: | ---: | ---: | ---: |
| `u64` multiply, 8,192 rows | 1.732 | 0.979 | 0.992 | 0.998 |
| Nullable `i64` add, 32,768 rows | 1.006 | 1.004 | 1.154 | 1.005 |

The actual framework pair order was imbalanced: D ran before F in 16
blocks and after it in five. Treat these figures as diagnostic
observations, not reliable causal effect sizes.

EC2 C7g (Neoverse V1), C6a (EPYC 7R13), and C7i (Xeon Platinum 8488C).
Rust 1.99.1 / LLVM 23.1.1, bench profile with 16 CGUs and no LTO,
MiMalloc, CPU 2 pinned. The same 455-case public-path microbenchmark
suite ran serially across hosts, with 21 paired blocks, 10 ms per case,
A/A calibration, and a separate control executable. These are
microbenchmarks, not query results. C7i can dispatch to AVX-512 at
runtime even in its AVX2 build.

</details>

The separately confirmed NEON comparison gains were published in #10843,
which has merged. They do not measure this framework.

CI passed on `b0a64dd`, including all-target, all-feature Clippy with
warnings denied and the Linux, Windows, sanitizer, and Miri test jobs.
The CodSpeed check fails on the flags described above.

---------

Signed-off-by: Connor Tsui <[email protected]>
@connortsui20
connortsui20 deleted the ct/row-fn-cleanup/primitive-compare-on-develop branch November 6, 2026 15:40
connortsui20 added a commit that referenced this pull request Nov 12, 2026
Tracking Issue: #9128

## Summary

Moves primitive comparisons onto the shared RowFn executor from #10675,
so comparison code only selects the predicate while the framework
handles decoding, constants, validity, and Boolean output.

## Changes

Preserves the public `Binary` identity and floating-point total
ordering. Uses borrowed operands and dtypes, and moves a constant left
operand to the right with the swapped operator. Relies on the existing
`primitive_comparisons_across_bitmap_words` test for all six swapped
operators and keeps the existing comparison benchmarks.

Based on #10675. Keeps the wide-type, sliced-input, bitmap-boundary,
NaN, and signed-zero tests added upstream in #10843.

## Performance

CodSpeed compares this PR with #10675 (`b0a64dd`). Nine primitive
comparison benchmarks are 12% to 31% faster in walltime, led by
`compare_u64_avx2` and `compare_float_avx2` at 31% and 28%, and no
comparison benchmark regresses.

These gains depend on #10675. #11220 measured the same two commits
directly on `develop`. Without #10675, comparisons on small arrays were
10% to 16% slower than `develop` in CPU simulation, and only two
walltime gains remained, at 16% and 18%. The #10675 description has the
full comparison.

The baseline already includes the private NEON packing kernel from
#10843, which this PR replaces with RowFn execution. So the gains above
are measured against that improved baseline, and the old packing gains
aren't credited to this PR.

<details>
<summary>CodSpeed comparison benchmarks against #10675</summary>

CodSpeed marks the walltime runners as unreliable, but all nine cases
move in the same direction.

| Benchmark | Faster by |
| --- | ---: |
| `compare_u64_avx2` | 30.8% |
| `compare_float_avx2` | 28.2% |
| `lt_i64_nullable_avx2` | 18.4% |
| `compare_int_constant_neon` | 16.2% |
| `compare_int_eq_avx2` | 15.4% |
| `compare_f32_avx2` | 15.4% |
| `compare_int_constant_avx2` | 15.3% |
| `compare_int_avx2` | 13.5% |
| `compare_int_nullable_avx2` | 12.5% |

</details>

<details>
<summary>Historical EC2 accumulated-stack measurements</summary>

These runs measured an earlier accumulated stack, not this layer in
isolation or the current head. They showed gains on some configurations
and regressions on others.

Ratios are the median of 21 paired candidate/baseline elapsed-time
ratios. Below 1 is faster. This compares the earlier complete migration,
M0 `ddf8a16df6b7d7c20cfd860e5f23438d97ca4238`, with D
`0291778db36809f77ce0219bf9850506d3cba3b6`. That baseline predates
#10843. The array-pair cases use all-valid nullable inputs. The addition
case is an arithmetic control whose algorithm was unchanged.

| Workload | C7g NEON | C6a AVX2 | C7i AVX2 build | C7i AVX-512 build |
| --- | ---: | ---: | ---: | ---: |
| `f32` array-pair Gte, 8,192 rows | 1.014 | 1.040 | 0.873 | 1.036 |
| `i64` array-pair Gte, 8,192 rows | 0.900 | 1.139 | 0.929 | 0.943 |
| Nullable `i64` add, 32,768 rows | 1.002 | 1.123 | 1.154 | 1.004 |

For example, the old stack took about 10% less time for the ARM `i64`
comparison, but about 14% more for the same case on AVX2-only C6a. C6a
nullable `i64` addition took about 12% more time. These whole-executable
measurements do not establish that the comparison algorithm caused
movement in arithmetic controls.

In the four-label matrix, D ran before M0 in 11 blocks and after it in
ten. The experimental one-pass decoder variants are excluded from this
table.

EC2 C7g (Neoverse V1), C6a (EPYC 7R13), and C7i (Xeon Platinum 8488C).
Rust 1.99.1 / LLVM 23.1.1, bench profile with 16 CGUs and no LTO,
MiMalloc, CPU 2 pinned. The same 455-case public-path microbenchmark
suite ran serially across hosts, with 21 paired blocks, 10 ms per case,
A/A calibration, and a separate control executable. These are
microbenchmarks, not query results. C7i can dispatch to AVX-512 at
runtime even in its AVX2 build.

Saved evidence is in the local investigation archive
`row-fn-evaluation-20261026`: `manifest.json`, `*-matrix-*-medians.csv`,
`*-matrix-*-summary.json`, and `results/<host>/results/matrix-*.json`.

</details>

<details>
<summary>Confirmed independent packing gains, now in develop via
#10843</summary>

The ARM-only private-kernel change reduced long array-pair cohort time
on Graviton3 as follows. Each cohort contains 32 cases, including
partially valid inputs and lengths around 16K.

| Cohort | Confirmation elapsed-time ratio | Paired 95% interval | Time
reduction |
| --- | ---: | --- | ---: |
| `i64` | 0.846 | 0.843 to 0.856 | 15.4% |
| `u64` | 0.860 | 0.858 to 0.864 | 14.0% |
| `f64` | 0.882 | 0.880 to 0.886 | 11.8% |

Baseline `a59294e35e7109fac894a7a38a68f42db0c3c967`, candidate
`f018aaeef739145e1bc50079a441c5d9fd53404a`. The cohort uses a geometric
mean of case ratios within each block, then the median across blocks.
Two 21-block sessions used complementary AB/BA orders, the same full
455-case suite, and 1,000 single-iteration samples per case. The initial
fixed-order source comparisons were discarded. The AVX2 builds had
identical `.text` bytes, so no x86 speedup was attributed to this
change. See #10843 for the harness and full methodology.

These gains required neither this PR nor #10675.

</details>

On `e724610`, CI passed all-target, all-feature Clippy with warnings
denied and the Linux, CPU feature, sanitizer, and Miri test jobs.

---------

Signed-off-by: Connor Tsui <[email protected]>
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.

1 participant