Repository navigation
Conversation
Merging this PR will regress 2 benchmarks
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | scalar_f32_argminmax_masked |
1.1 ms | 1.2 ms | -5.79% |
| ❌ | scalar_f64_argminmax_masked |
1.5 ms | 1.5 ms | -4.14% |
| ⚡ | scalar_f32_argminmax_rn |
959.7 µs | 905.8 µs | +5.95% |
| ⚡ | scalar_f32_argminmax_in |
848.7 µs | 801.2 µs | +5.93% |
| ⚡ | scalar_f32_argmax_in |
770 µs | 737.4 µs | +4.42% |
| ⚡ | scalar_f32_argmin_in |
770 µs | 737.4 µs | +4.42% |
| ⚡ | scalar_f64_argminmax_in |
1.2 ms | 1.2 ms | +3.87% |
| ⚡ | scalar_f64_argminmax_rn |
1.3 ms | 1.3 ms | +3.71% |
| ⚡ | scalar_f64_argmin_in |
1.1 ms | 1.1 ms | +2.27% |
| ⚡ | scalar_f64_argmax_in |
1.1 ms | 1.1 ms | +2.27% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing perf/scalar-first-non-nan (fea95ff) with main (f68aba5)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The SIMD functions run a scalar loop on slices shorter than one vector, and on the elements after the last full vector. For the floats that ignore NaNs (
ArgMinMaxon f32 and f64), that loop tests afirst_non_nan_updateflag at every value. Because of that flag, LLVM compiles the update of the minimum (maximum) as a branch instead of a conditional move. On random data that branch mispredicts: about 2 ns per element, against 0.3-0.6 ns for a plainif v > max { max = v; index = i }loop. On an i7-1185G7 (AVX-512, 16 f32 lanes),argmaxon 2-15 random f32 values takes 9-30 ns; such a plain loop takes 2-8 ns.This is why candle (huggingface/candle#4051) calls argminmax only for rows of at least 128 elements.
Changes
SCALARInit::_init_min,_init_maxand_allow_first_non_nan_updatebecome one_start_index(0 forIntandFloatReturnNaN). The f16/bf16 scalar code already works this way.argmin/argmaxnow compile without branches in the loop (AVX2 f32argmax: 109 → 53 jumps).argminmaxkeeps itsif .. else if ..: two separateifs made integer and i128argminmaxslower.Checked
cargo test(all features and no default features) on an M5 (NEON). On an i7-1185G7: native debug, release and no default features, and Intel SDE with-spr,-hsw,-nhmand an AVX-512F-only CPU. Native on a Ryzen 9 5950X. Clippy as in CI, also for x86_64, i686, armv7 and wasm32.argmin,argmaxandargminmax; every length from 1 to 72, plus 127, 128, 135, 143, 1000 and 4096.argminbuild was 3.7 ns slower per call at every length from 8; the effect followed whichever copy sat at that address.argminmax: 0.84-1.0x. Integers: unchanged. Long slices: unchanged, or faster where main leaves 8-15 elements to the scalar loop (1000 f32 on AVX-512: 0.8x, 69 → 58 ns).maxssthrough the compare to the indexcmov. That applies to the publicSCALARtype, to wasm32 and ARMv7 withoutnightly_simd, and to f32 on x86 without SSE4.1 and f64 without AVX. 4096 f32 or f64 values,argmin/argmax:argmaxtook 8.3 µs on the i7 (now 3.6 µs) and 9.1 µs on the Ryzen (now 1.7 µs).argmin/argmaxno longer depend on the data (after any leading NaNs, which the scan for the first non-NaN value reads first), but on Intel they are slower for random data.argminmaxkeeps itsif .. else if ..branches. CodSpeed's scalar benchmarks may show this.