Skip to content

Compare primitive values through the shared RowFn executor - #10676

Draft
connortsui20 wants to merge 8 commits into
ct/row-fn-cleanup/fused-executionfrom
ct/row-fn-cleanup/primitive-compare
Draft

connortsui20 wants to merge 8 commits into
ct/row-fn-cleanup/fused-executionfrom
ct/row-fn-cleanup/primitive-compare

Conversation

@connortsui20

@connortsui20 connortsui20 commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Summary

Replaces the primitive comparison migration in #9979, using the shared executor from #10675. This PR depends on #10675 and has been replayed onto its rebased head 7c47d4bcfee3370684c1b22de999e61f66628afa.

Reopened as a draft. The controlled measurements showed substantial AVX2 gains as well as ARM and AVX-512 regressions. They justify further work before merging, but do not establish that this migration has no benefit. #10741 isolates some float32 packing gains without this migration, while #10742 explores a smaller validity change. Neither is a complete replacement for every observed benefit here.

Changes

The private row kernel selects the primitive type and comparison operator. The framework owns borrowed inputs, constant handling, strict validity, and packed Boolean output. Removes the primitive operand wrapper and handwritten primitive kernels while preserving the public Binary identity and Vortex's floating-point total ordering.

Native Graviton3 measurements showed regressions when this migration used only #10673. Removing only the fused-execution patch while keeping all three foundations also slowed the tested comparisons. The dependency on #10675 keeps that execution work available when this migration merges.

Existing comparison tests exercise the shared executor. Source range-diff review confirms that the migration patch is unchanged by the rebase. Tests, formatting, and benchmarks were not rerun on the rebased head, so earlier measurements are not validation of this revision.

@connortsui20
connortsui20 added this pull request to stack #10677 October 21, 2026 15:10
@codspeed

codspeed Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Merging this PR will regress 17 benchmarks

4 warnings, 1 note

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

⚠️ Unable to measure the execution of 2 benchmarks

These benchmarks measured no execution time, so their results are not comparable and count as unchanged. This usually happens when the compiler removes the code under test.

Prevent compiler optimizations

See affected benchmarks

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

ℹ️ 40 benchmarks have no flamegraph

The flamegraph wasn't generated: the symbols of the profiling trace could not be resolved, because no information about the language frames was collected.

See affected benchmarks

⚡ 23 improved benchmarks
❌ 17 regressed benchmarks
✅ 2368 untouched benchmarks
⏩ 12 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Walltime threshold_neon[8] 1.4 µs 2.8 µs -50.78%
❌ Walltime compare_u8_neon 1.7 µs 3.1 µs -43.67%
❌ Walltime filter_neon[8] 967 ns 1,346 ns -28.16%
❌ Walltime filter_neon[64] 1.8 µs 2.4 µs -24.02%
❌ Walltime threshold_neon[64] 2.6 µs 3.4 µs -23.01%
❌ Walltime threshold_neon[80] 3.4 µs 4.4 µs -22.55%
❌ Walltime compare_int_neon 4.4 µs 5.3 µs -16.49%
❌ Walltime compare_int_constant_neon 3.9 µs 4.7 µs -16.23%
❌ Walltime compare_int_eq_neon 4.5 µs 5.3 µs -15.59%
❌ CPU Simulation filter_powerlaw_by_mostly_false[250000] 137.4 µs 158.1 µs -13.08%
❌ Walltime compare_int_nullable_neon 4.8 µs 5.6 µs -12.95%
❌ Walltime filter_neon[80] 2.5 µs 2.9 µs -12.13%
❌ CPU Simulation density_sweep_dense_runs[0.001] 52.3 µs 59 µs -11.36%
❌ CPU Simulation filter_random_by_mostly_false[250000] 162.5 µs 183.1 µs -11.23%
❌ CPU Simulation filter_random_by_power_law[250000] 165.6 µs 184.7 µs -10.36%
❌ CPU Simulation filter_powerlaw_by_power_law[250000] 165.8 µs 184.9 µs -10.36%
❌ CPU Simulation filter_powerlaw_by_random[250000] 152.5 µs 170 µs -10.27%
⚡ Walltime compare_f32_avx2 4.5 µs 2.4 µs +84.35%
⚡ Walltime compare_f32_avx512 3.4 µs 2.3 µs +48.81%
⚡ Walltime compare_u64_avx2 3.8 µs 2.8 µs +36.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-cleanup/primitive-compare (393ac0c) with ct/row-fn-cleanup/fused-execution (69f5fc0)

Open in CodSpeed

Footnotes

  1. 12 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 force-pushed the ct/row-fn-cleanup/primitive-compare branch from dee1a57 to 3a38935 Compare October 22, 2026 14:09
@connortsui20
connortsui20 removed this pull request from stack #10677 October 22, 2026 14:10
@connortsui20
connortsui20 changed the base branch from ct/row-fn-cleanup/fused-execution to ct/row-fn-cleanup/batch-inputs October 22, 2026 14:10
@connortsui20
connortsui20 added this pull request to stack #10695 October 22, 2026 14:11
@connortsui20
connortsui20 force-pushed the ct/row-fn-cleanup/primitive-compare branch from 3a38935 to 33ccf9b Compare October 22, 2026 14:49
@connortsui20 connortsui20 added the changelog/performance A performance improvement label Oct 22, 2026
@connortsui20
connortsui20 removed this pull request from stack #10695 October 22, 2026 15:12
@connortsui20
connortsui20 force-pushed the ct/row-fn-cleanup/primitive-compare branch from 33ccf9b to b837021 Compare October 22, 2026 15:13
@connortsui20
connortsui20 changed the base branch from ct/row-fn-cleanup/batch-inputs to ct/row-fn-cleanup/fused-execution October 22, 2026 15:13
@connortsui20
connortsui20 added this pull request to stack #10697 October 22, 2026 15:13

connortsui20 commented Oct 23, 2026 •

Copy link
Copy Markdown
Member Author

Edit: reopened as a draft. The controlled measurements showed substantial AVX2 gains as well as ARM and AVX-512 regressions. Those results justify holding the PR for further work, but do not establish that the migration has no benefit. The earlier closure treated those two conclusions as equivalent.

#10741 isolates a useful float32 packing policy, and #10742 explores a smaller validity change. Neither replaces every benefit observed in the migration. Both remain independent drafts with their own measurement limits.

#10675 and this PR are being rebased while preserving their dependency. The saved measurements are historical evidence, not validation of the rebased heads.

connortsui20 and others added 4 commits October 25, 2026 22:54
Keep the primitive kernel focused on type and operator dispatch. Delegate borrowed inputs, constants, strict validity, and packed Boolean output to the lower stack layers while preserving Binary identity and floating-point total ordering.

Replaces the comparison migration in #9979.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
A constant first operand vectorizes worse in the packed Boolean row loop, which made
8-bit constant-left comparisons slower than the native kernel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Robert Kruszewski <github@robertk.io>
The multiversioned collector used by primitive comparison always filled one byte per row and then
packed the bytes. For 64-bit lanes the narrowing to bytes costs more than accumulating the bits in a
register: on every AVX2 row, and on AVX-512 when a batch constant leaves a single load per lane.
Those shapes now accumulate bits directly. Every other shape, and aarch64, keeps the fill-and-pack
loop, which stays faster there.

The collector stays out of line. Inlined into a large caller, a statically enabled AVX-512 loop can
spill its byte constant to a masked memory load that stalls when it aliases the output stores.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Robert Kruszewski <github@robertk.io>
For a batch of at most 16Ki rows whose input validities are materialized Boolean arrays, one
word-wise `and` costs less than building and optimizing a lazy `and` node. Larger batches keep the
lazy node, which defers the work to a reader that may never come.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Robert Kruszewski <github@robertk.io>
@robert3005
robert3005 force-pushed the ct/row-fn-cleanup/primitive-compare branch from 3583fc2 to 237dfc0 Compare October 26, 2026 00:51
Select the packing loop from the `fearless_simd::Level` detected for the CPU instead of separate
feature checks. Each level calls a `#[target_feature]` loop that takes the row callback by value:
captured in a closure environment, the callback hides its batch-constant source from LLVM, which
then does not unswitch the 8-bit loop. The selection rules are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Robert Kruszewski <github@robertk.io>
robert3005 and others added 3 commits October 26, 2026 02:18
A direct Boolean batch used to inspect its inputs before dispatch and decode them again inside the
visit. Each step read the same lengths, dtypes and constant encodings, and the inspection cloned the
argument dtypes into a list. Decode a direct batch in one pass after dispatch instead: classify
literal constants by encoding, decode them from the scalar already read, and conjoin column
validities, taking the first one unchanged. A batch that is not direct, including one with a null
literal, a wrapped constant or a wrong row count, falls back to batch execution, which inspects it
lazily and keeps its errors.

Inputs that already hold their dtypes contiguously lend them to dispatch, so batch execution does
not clone them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VT4SKVtrb3NMjXkMj4vW6S
Signed-off-by: Robert Kruszewski <github@robertk.io>
Build the two operand dtypes as primitive values once and lend them to dispatch, and detect a
constant left operand by its encoding instead of two dynamic downcasts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VT4SKVtrb3NMjXkMj4vW6S
Signed-off-by: Robert Kruszewski <github@robertk.io>
`InputElement::decode_constant_ref` avoided one reference count per batch constant but measured
no change in instructions per comparison. Direct batches decode constants through
`decode_constant_scalar`, so the borrowed variant is only an indirection. Its default now decodes
the owned array directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VT4SKVtrb3NMjXkMj4vW6S
Signed-off-by: Robert Kruszewski <github@robertk.io>

This branch has not been deployed

No deployments
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