Skip to content

perf: Vectorize float normalization check - #26956

Open
mkleen wants to merge 7 commits into
apache:mainfrom
mkleen:vectorize-float-normalization-check
Open

mkleen wants to merge 7 commits into
apache:mainfrom
mkleen:vectorize-float-normalization-check

Conversation

@mkleen

@mkleen mkleen commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

normalize_floats runs on every float key column before it is compared, hashed or grouped. It first scans the column for values that need rewriting. When there are none, which is the common case, it returns the input unchanged, so that scan is the whole cost.

The scan used an early-exit iter().any(). Because the loop can stop at any value, the compiler checks one value at a time and doesn't vectorize it.

Results from the float_normalization benchmark on 8192 values from my local machine (Apple M1 Max):

┌─────────────────────┬─────────┬─────────┬────────────────┐
│        Case         │  main   │   PR    │     Change     │
├─────────────────────┼─────────┼─────────┼────────────────┤
│ f64, clean          │ 2.62 µs │ 0.71 µs │ −73% (3.7x)    │
├─────────────────────┼─────────┼─────────┼────────────────┤
│ f32, clean          │ 2.63 µs │ 0.34 µs │ −87% (7.7x)    │
├─────────────────────┼─────────┼─────────┼────────────────┤
│ f16, clean          │ 2.62 µs │ 0.18 µs │ −93% (14.7x)   │
├─────────────────────┼─────────┼─────────┼────────────────┤
│ f64, -0.0 first row │ 1.66 µs │ 1.75 µs │ +5%            │
├─────────────────────┼─────────┼─────────┼────────────────┤
│ f32, -0.0 first row │ 0.74 µs │ 0.79 µs │ +7%            │
├─────────────────────┼─────────┼─────────┼────────────────┤
│ f16, -0.0 first row │ 0.31 µs │ 0.33 µs │ +7–14% (noisy) │
├─────────────────────┼─────────┼─────────┼────────────────┤
│ f64, -0.0 last row  │ 4.23 µs │ 2.35 µs │ −44%           │
├─────────────────────┼─────────┼─────────┼────────────────┤
│ f32, -0.0 last row  │ 3.35 µs │ 1.09 µs │ −68%           │
├─────────────────────┼─────────┼─────────┼────────────────┤
│ f16, -0.0 last row  │ 2.98 µs │ 0.51 µs │ −83%           │
└─────────────────────┴─────────┴─────────┴────────────────┘

What changes are included in this PR?

  • The scan in normalize_primitive now checks the values in chunks of 1024. Inside a chunk it folds over every value with no early exit, so the compiler turns it into a SIMD OR-reduction. It stops after the first chunk that finds a value to rewrite.
  • When a -0.0 is near the start, the whole first chunk is still checked before the scan stops. That adds 20–90 ns, and the column is copied right after anyway.
  • A new float_normalization benchmark covers f64, f32 and f16, with nothing to rewrite, a -0.0 in the first row, and a -0.0 in the last row.

What is the testing strategy for this PR?

The behavior is unchanged. The existing normalize_floats unit tests in float_semantics.rs and the float sqllogictests cover both the case with nothing to normalize and the rewrite path.

Are there any user-facing changes?

No.

LLM-generated code disclosure

This PR includes LLM-generated code and comments. All LLM-generated content has been manually reviewed.

@github-actions github-actions Bot added the common Related to common crate label Oct 30, 2026
@mkleen
mkleen force-pushed the vectorize-float-normalization-check branch from 9a16045 to 05dce65 Compare October 30, 2026 21:36
Measures normalize_floats, which runs on every float key column
before it is compared, hashed or grouped.
`normalize_floats` first scans a float column for values that need
rewriting and returns it unchanged when there are none, the common case.
The scan used an early-exit `iter().any()`, which checks one value at a
time and does not vectorize.

Fold over every value instead. The fold has no early exit, so the compiler
turns it into a SIMD OR-reduction. It always scans the whole column, but
that only costs extra when the column has a value to rewrite, and then the
column is copied anyway.
@mkleen
mkleen force-pushed the vectorize-float-normalization-check branch from 05dce65 to 5bd940c Compare October 30, 2026 21:40
@mkleen
mkleen marked this pull request as ready for review October 30, 2026 21:41
@jayzhan211
jayzhan211 self-requested a review October 31, 2026 00:39
@jayzhan211

Copy link
Copy Markdown
Contributor

Benchmarks are available #26954 and must be merged first

I think given the complexity we don't need to split PRs that much

@mkleen

mkleen commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Benchmarks are available #26954 and must be merged first

I think given the complexity we don't need to split PRs that much

fair point :-)

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

Thank you @mkleen

Left few comments please take a look

if !arr.values().iter().any(|v| v.needs_normalization()) {
// Not `any()`: its early exit checks one value at a time, while a fold
// over every value vectorizes.
let needs_normalization = arr

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.

The fold now always reads the whole column, even when a -0.0 is in the first row. Folding in fixed chunks (e.g. 512 values) and stopping after the first chunk that finds one would keep the SIMD speedup and still exit early.

The first-row case is the one where the check can stop right away.
The fold over the whole column vectorizes, but it always reads every value,
even when the first row is a -0.0. Fold over chunks of 1024 values instead
and stop after the first chunk with a match. Each chunk still vectorizes,
and a -0.0 near the start no longer costs a full scan.
Name the chunk size, so the test follows it if it is retuned, cover
Float16 and Float32 as well, and describe the chunked check in the
normalize_floats docs.
@mkleen
mkleen force-pushed the vectorize-float-normalization-check branch from 94a403a to e294cf8 Compare October 31, 2026 08:19
@mkleen

mkleen commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the review @kumarUjjawal!

I addressed your feedback:

  • Early exit (float_semantics.rs)
  • -0.0 near the start, and Float16 (float_normalization.rs) for the benchmarks.

I updated the benchmark run in the pr description.
We actually have a small regression with the cases you pointed out when -0.0 is the first row.

@mkleen
mkleen requested a review from kumarUjjawal October 31, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

common Related to common crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants