Repository navigation
[codex] perf: extend small dictionary take across NEON value widths - #10315
joseph-isaacs wants to merge 4 commits into
Conversation
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Merging this PR will regress 3 benchmarks
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | WallTime | mul_u64_nonnull_neon |
29.1 µs | 39.6 µs | -26.38% |
| ❌ | WallTime | multiply_shapes_neon[(32768, PerRowPerRow)] |
32.9 µs | 38.8 µs | -15.34% |
| ❌ | WallTime | mul_i64_nonnull_neon |
33 µs | 38.7 µs | -14.91% |
| ⚡ | Simulation | density_sweep_dense_runs[0.9] |
56.1 µs | 18.1 µs | ×3.1 |
| ⚡ | Simulation | decode_primitives[u8, (2000, 32)] |
44.1 µs | 34.1 µs | +29.45% |
| ⚡ | Simulation | decode_primitives[u8, (1000, 32)] |
36.7 µs | 32.5 µs | +12.72% |
| 🆕 | WallTime | skewed_avx512[16, 1000000] |
N/A | 51 µs | N/A |
| 🆕 | WallTime | skewed_avx512[16, 1024] |
N/A | 1 µs | N/A |
| 🆕 | WallTime | skewed_avx512[16, 64] |
N/A | 985 ns | N/A |
| 🆕 | WallTime | skewed_avx512[16, 65536] |
N/A | 3.3 µs | N/A |
| 🆕 | WallTime | skewed_avx512[2, 1000000] |
N/A | 51.2 µs | N/A |
| 🆕 | WallTime | skewed_avx512[2, 1024] |
N/A | 1 µs | N/A |
| 🆕 | WallTime | skewed_avx512[2, 64] |
N/A | 1 µs | N/A |
| 🆕 | WallTime | skewed_avx512[2, 65536] |
N/A | 3.1 µs | N/A |
| 🆕 | WallTime | skewed_avx512[32, 1000000] |
N/A | 49.5 µs | N/A |
| 🆕 | WallTime | skewed_avx512[32, 1024] |
N/A | 1 µs | N/A |
| 🆕 | WallTime | skewed_avx512[32, 64] |
N/A | 994 ns | N/A |
| 🆕 | WallTime | skewed_avx512[32, 65536] |
N/A | 3 µs | N/A |
| 🆕 | WallTime | skewed_avx512[4, 1000000] |
N/A | 51.1 µs | N/A |
| 🆕 | WallTime | skewed_avx512[4, 1024] |
N/A | 1 µs | N/A |
| ... | ... | ... | ... | ... | ... |
ℹ️ 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 ji/small-dict-take-kernel (3c0a33e) with develop (5079a20)
Footnotes
-
518 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. ↩
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Too many benchmarks in a single uploadThe performance report could not be generated because there were too many benchmarks in a single upload to CodSpeed. We recommend sharding your benchmarks into smaller uploads, with max 1000 benchmarks per upload. See the documentation for more information. |
Superseded by #10317. The expanded NEON kernels and benchmarks have been consolidated there in commit 04f4c4e, alongside its broader x86 implementation. The original branch is retained for benchmark provenance.
Summary
Extend small-dictionary take beyond the recently merged AVX2 and NEON 16-entry byte tables. Keep the AVX2/VBMI extension to 32 byte values and broaden NEON to fixed-width values and larger dictionaries with u8 codes.
Every cardinality within each limit is supported, including non-powers of two. Wider code types and unsupported value widths/cardinalities retain the existing fallback. The 16- and 32-byte record experiments did not beat scalar copying, so those widths remain on the fallback. Variable-length strings and bit-packed booleans are outside this fixed-width path.
Implementation
VPSHUFBlookups and a blend for 17–32 byte values.VPERMBfor 17–32 byte values when supported.TBL, then multiple 64-byte banks combined with OR for dictionaries through 256 bytes. Independent maximum-code accumulators shorten the bounds-check dependency chain.ST2/ST4. This avoids repeating a multi-bank lookup for every output byte.fearless_simd1.0.0. Its dependency is outside the production workspace. It directly includes the production NEON loops and implements matching byte-table and transposed-table generic kernels.NEON — Apple M3 Max
End-to-end dictionary execution, including allocation, one million uniform non-null u8 codes. Times are microseconds. Rust 1.98.0, native aarch64 macOS, no CPU pinning.
Baseline is 3c0a33e with the expanded benchmark source. It already has the earlier PR's 32-entry byte fast path; larger byte tables and wider values use the scalar default. Candidate is cac5eca.
Entries are medians of three run medians, with 31 samples of four iterations. Run order was baseline/candidate, candidate/baseline, baseline/candidate. All 1,408 cases ran in each executable: 11 primitive types × 16 cardinalities × four lengths × two distributions.
The host showed scheduling variation, including in unchanged fallback cases. For example, u64/32 candidate run medians were 166.5, 156.4, and 325.3 µs. Small differences at 64 rows should not be treated as stable wins. These measurements establish gains on this M3 Max; they are not measurements of other NEON implementations.
Native NEON versus generic fearless_simd
Kernel-only comparison on the same M3 Max with Rust 1.98.0, one million uniform codes. Times are microseconds. Output is preallocated; table preparation, dispatch, bounds checks, lookup, and tails are timed. The generator differs from the end-to-end benchmark, so compare columns within this table.
The 16-/32-byte native entries are experimental kernels, not enabled production paths. Most enabled cases are effectively tied between native and generic SIMD. The byte/16 native loop uses four independent accumulators while the generic version uses a single 16-lane vector; this comparison applies to the supplied kernels, not to the libraries in general.
Each entry is the median of three run medians. Each run rotates the three variants over 31 samples, with
clamp(2_000_000 / output_bytes, 1, 256)iterations per sample. All 504 datasets and setup correctness checks ran successfully.AVX — Intel Xeon 6975P-C
Measured over SSH on
vortex-ec2, pinned to CPU 2 withtaskset. These are the previously collected measurements from a0aaec6, before the module cleanup; they are not a measurement of the latest PR head.The 32-value case is approximately 7.7× faster. A separately measured AVX2-only variant took 37.62 µs, so this improvement is available without AVX-512.
The scalar diagnostic variant bypassed small-table dispatch; the AVX2-only diagnostic variant disabled VBMI selection. Each variant was built and saved as a separate executable. Baseline and diagnostic entries are medians of three runs in alternating order, with 200 samples of 20 iterations. The final x86 dispatch was measured once with those settings.
Reproduction and validation
For the end-to-end comparison, keep
take_small_dict.rsidentical in the baseline and candidate and save each emitted executable:For the generic comparison, on AArch64:
cargo run --release --manifest-path benchmarks/take-small-dict/Cargo.toml > results.jsonlSee the comparison README for timing boundaries, data generation, and dispatch limits.
vpermb. No new x86 performance measurements were collected for this extension.