Skip to content

Speed up ARM fixed-width array scans - #54

Merged
mrecachinas merged 1 commit into
mainfrom
perf/arm-cross-record-simd
Aug 2, 2026
Merged

Speed up ARM fixed-width array scans#54
mrecachinas merged 1 commit into
mainfrom
perf/arm-cross-record-simd

Conversation

@mrecachinas

Copy link
Copy Markdown
Owner

What

Adds ARM NEON cross-record scanners for 16- and 32-byte array elements used by the first, best, and all APIs. The optimized scanners preserve first-index ordering, lowest-index best ties, threshold behavior, exact-match termination, and ordered results.

The Rayon crossover for these widths moves from 5 MiB to 16 MiB because the faster serial scanner makes parallel scheduling beneficial later. Benchmark and randomized semantic coverage are expanded for fixed-width workloads.

Closes the ARM/cross-record portion of #51.

Performance

Three runs per revision on the same Apple M4 Max, comparing median run medians:

Case Before After Delta
Rust 16B random/no-match first 1.596 us 397 ns 75% faster
Rust 16B random/no-match all 1.382 us 524 ns 62% faster
Rust 16B exact-mid best 808 ns 217 ns 73% faster
Rust 32B random/no-match first 2.003 us 798 ns 60% faster
Rust 32B random/no-match all 1.955 us 1.048 us 46% faster
Python 16B random/no-match all 1.420 us 565 ns 60% faster
Python 32B random/no-match all 2.037 us 852 ns 58% faster

Testing

  • cargo test --no-default-features — 76 unit and 2 doc tests
  • python3 -m pytest -vls . — 187 tests
  • cargo fmt --check
  • ruff format --check .
  • Required public API validation scenarios

ruff check . continues to report the pre-existing EXE001 warning for test/test_hexhamming.py.

Add NEON cross-record scanners for 16- and 32-byte records, expand semantic and benchmark coverage, and retune the parallel crossover based on three-run measurements.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 01071e57-12fd-4f42-bc67-e2b274d0b007
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

📊 Benchmark Comparison Results

Status Benchmark Base PR Delta
🆕 fixed_width_array_scan_matrix[all-exact_early-16] N/A 0.0063ms NEW
🆕 fixed_width_array_scan_matrix[all-exact_early-32] N/A 0.0072ms NEW
🆕 fixed_width_array_scan_matrix[all-exact_late-16] N/A 0.0063ms NEW
🆕 fixed_width_array_scan_matrix[all-exact_late-32] N/A 0.0073ms NEW
🆕 fixed_width_array_scan_matrix[all-exact_mid-16] N/A 0.0063ms NEW
🆕 fixed_width_array_scan_matrix[all-exact_mid-32] N/A 0.0073ms NEW
🆕 fixed_width_array_scan_matrix[all-random_no_match-16] N/A 0.0062ms NEW
🆕 fixed_width_array_scan_matrix[all-random_no_match-32] N/A 0.0072ms NEW
🆕 fixed_width_array_scan_matrix[all-threshold_d-16] N/A 0.0063ms NEW
🆕 fixed_width_array_scan_matrix[all-threshold_d-32] N/A 0.0073ms NEW
🆕 fixed_width_array_scan_matrix[all-threshold_d_minus_1-16] N/A 0.0062ms NEW
🆕 fixed_width_array_scan_matrix[all-threshold_d_minus_1-32] N/A 0.0072ms NEW
🆕 fixed_width_array_scan_matrix[all-threshold_d_plus_1-16] N/A 0.0063ms NEW
🆕 fixed_width_array_scan_matrix[all-threshold_d_plus_1-32] N/A 0.0073ms NEW
🆕 fixed_width_array_scan_matrix[best-exact_early-16] N/A 0.0001ms NEW
🆕 fixed_width_array_scan_matrix[best-exact_early-32] N/A 0.0001ms NEW
🆕 fixed_width_array_scan_matrix[best-exact_late-16] N/A 0.0066ms NEW
🆕 fixed_width_array_scan_matrix[best-exact_late-32] N/A 0.0075ms NEW
🆕 fixed_width_array_scan_matrix[best-exact_mid-16] N/A 0.0034ms NEW
🆕 fixed_width_array_scan_matrix[best-exact_mid-32] N/A 0.0039ms NEW
🆕 fixed_width_array_scan_matrix[best-random_no_match-16] N/A 0.0065ms NEW
🆕 fixed_width_array_scan_matrix[best-random_no_match-32] N/A 0.0075ms NEW
🆕 fixed_width_array_scan_matrix[best-threshold_d-16] N/A 0.0066ms NEW
🆕 fixed_width_array_scan_matrix[best-threshold_d-32] N/A 0.0075ms NEW
🆕 fixed_width_array_scan_matrix[best-threshold_d_minus_1-16] N/A 0.0065ms NEW
🆕 fixed_width_array_scan_matrix[best-threshold_d_minus_1-32] N/A 0.0075ms NEW
🆕 fixed_width_array_scan_matrix[best-threshold_d_plus_1-16] N/A 0.0066ms NEW
🆕 fixed_width_array_scan_matrix[best-threshold_d_plus_1-32] N/A 0.0075ms NEW
🆕 fixed_width_array_scan_matrix[first-exact_early-16] N/A 0.0001ms NEW
🆕 fixed_width_array_scan_matrix[first-exact_early-32] N/A 0.0001ms NEW
🆕 fixed_width_array_scan_matrix[first-exact_late-16] N/A 0.0059ms NEW
🆕 fixed_width_array_scan_matrix[first-exact_late-32] N/A 0.0068ms NEW
🆕 fixed_width_array_scan_matrix[first-exact_mid-16] N/A 0.0031ms NEW
🆕 fixed_width_array_scan_matrix[first-exact_mid-32] N/A 0.0035ms NEW
🆕 fixed_width_array_scan_matrix[first-random_no_match-16] N/A 0.0059ms NEW
🆕 fixed_width_array_scan_matrix[first-random_no_match-32] N/A 0.0068ms NEW
🆕 fixed_width_array_scan_matrix[first-threshold_d-16] N/A 0.0031ms NEW
🆕 fixed_width_array_scan_matrix[first-threshold_d-32] N/A 0.0035ms NEW
🆕 fixed_width_array_scan_matrix[first-threshold_d_minus_1-16] N/A 0.0059ms NEW
🆕 fixed_width_array_scan_matrix[first-threshold_d_minus_1-32] N/A 0.0071ms NEW
🆕 fixed_width_array_scan_matrix[first-threshold_d_plus_1-16] N/A 0.0031ms NEW
🆕 fixed_width_array_scan_matrix[first-threshold_d_plus_1-32] N/A 0.0035ms NEW
➖ 62 benchmarks within noise (click to expand)
Benchmark Base PR Delta
check_bytes_arrays_all_within_dist[ 512 elems,s=16,at 0] 0.0033ms 0.0033ms no change
check_bytes_arrays_all_within_dist[ 512 elems,s=16,end] 0.0033ms 0.0033ms 0.0% faster
check_bytes_arrays_all_within_dist[ 512 elems,s=16,mid] 0.0033ms 0.0033ms 0.0% faster
check_bytes_arrays_all_within_dist[ 1024 elems,s=32,at 0] 0.0072ms 0.0072ms 0.1% slower
check_bytes_arrays_all_within_dist[ 1024 elems,s=32,end] 0.0073ms 0.0073ms 0.0% faster
check_bytes_arrays_all_within_dist[ 1024 elems,s=32,mid] 0.0073ms 0.0073ms 0.1% faster
check_bytes_arrays_all_within_dist[16384 elems,s=64,at 0] 0.1035ms 0.1034ms 0.0% faster
check_bytes_arrays_all_within_dist[16384 elems,s=64,end] 0.1084ms 0.1086ms 0.2% slower
check_bytes_arrays_all_within_dist[16384 elems,s=64,mid] 0.1038ms 0.1082ms 4.2% slower
check_bytes_arrays_best_within_dist[ 512 elems,s=16,at 0] 0.0001ms 0.0001ms 3.2% slower
check_bytes_arrays_best_within_dist[ 512 elems,s=16,end] 0.0036ms 0.0034ms 4.5% faster
check_bytes_arrays_best_within_dist[ 512 elems,s=16,mid] 0.0017ms 0.0017ms 0.2% slower
check_bytes_arrays_best_within_dist[ 1024 elems,s=32,at 0] 0.0075ms 0.0075ms 0.1% faster
check_bytes_arrays_best_within_dist[ 1024 elems,s=32,end] 0.0075ms 0.0075ms 0.0% faster
check_bytes_arrays_best_within_dist[ 1024 elems,s=32,mid] 0.0075ms 0.0075ms no change
check_bytes_arrays_best_within_dist[16384 elems,s=64,at 0] 0.1132ms 0.1083ms 4.4% faster
check_bytes_arrays_best_within_dist[16384 elems,s=64,end] 0.1134ms 0.1087ms 4.1% faster
check_bytes_arrays_best_within_dist[16384 elems,s=64,mid] 0.1132ms 0.1084ms 4.2% faster
check_bytes_arrays_first_within_dist[ 512 elems,s=16,at 0] 0.0001ms 0.0001ms 0.4% slower
check_bytes_arrays_first_within_dist[ 512 elems,s=16,end] 0.0030ms 0.0030ms 1.0% faster
check_bytes_arrays_first_within_dist[ 512 elems,s=16,mid] 0.0016ms 0.0016ms 0.4% slower
check_bytes_arrays_first_within_dist[ 1024 elems,s=32,at 0] 0.0001ms 0.0001ms 2.3% slower
check_bytes_arrays_first_within_dist[ 1024 elems,s=32,end] 0.0068ms 0.0072ms 4.5% slower
check_bytes_arrays_first_within_dist[ 1024 elems,s=32,mid] 0.0035ms 0.0035ms 0.6% slower
check_bytes_arrays_first_within_dist[16384 elems,s=64,at 0] 0.0002ms 0.0002ms 12.1% slower
check_bytes_arrays_first_within_dist[16384 elems,s=64,end] 0.1081ms 0.1134ms 4.9% slower
check_bytes_arrays_first_within_dist[16384 elems,s=64,mid] 0.0542ms 0.0540ms 0.3% faster
check_bytes_within_dist[127 bytes,d=500] 0.0001ms 0.0001ms 9.4% faster
check_bytes_within_dist[16 bytes,d=0] 0.0001ms 0.0001ms 9.7% faster
check_bytes_within_dist[64 bytes,d=100] 0.0001ms 0.0001ms 10.8% faster
hex_within_dist 0.0001ms 0.0001ms 7.7% faster
hd_bytes[1000-diff] 0.0001ms 0.0001ms 10.3% faster
hd_bytes[1000-same] 0.0001ms 0.0001ms 12.3% faster
hd_bytes[1024-diff] 0.0001ms 0.0001ms 1.2% faster
hd_bytes[1024-same] 0.0002ms 0.0001ms 55.1% faster
hd_bytes[3-diff] 0.0001ms 0.0001ms 2.8% faster
hd_bytes[3-same] 0.0001ms 0.0001ms 3.6% faster
hd_bytes[64-diff] 0.0001ms 0.0001ms 0.7% faster
hd_bytes_buffer[bytearray] 0.0001ms 0.0002ms 84.3% slower
hd_bytes_buffer[memoryview] 0.0001ms 0.0001ms 7.4% slower
hd_bytes_gil_boundary[16376] 0.0006ms 0.0006ms 3.2% slower
hd_bytes_gil_boundary[16384] 0.0006ms 0.0006ms 7.5% slower
hd_bytes_gil_boundary[16392] 0.0006ms 0.0006ms 7.5% slower
hd_bytes_random[1024] 0.0001ms 0.0001ms 1.6% faster
hd_bytes_random[16] 0.0001ms 0.0001ms 1.2% faster
hd_bytes_random[64] 0.0001ms 0.0001ms 11.5% faster
hd_string[1000-diff] 0.0002ms 0.0002ms 0.5% slower
hd_string[1000-same] 0.0002ms 0.0002ms 0.6% slower
hd_string[1024-diff] 0.0002ms 0.0002ms 2.8% faster
hd_string[1024-same] 0.0002ms 0.0002ms 2.9% faster
hd_string[3-diff] 0.0002ms 0.0002ms 5.0% faster
hd_string[3-same] 0.0001ms 0.0001ms 9.0% faster
hd_string[64-diff] 0.0001ms 0.0001ms 3.9% faster
hd_string_random[1024] 0.0002ms 0.0002ms 4.4% faster
hd_string_random[16] 0.0001ms 0.0001ms 1.0% slower
hd_string_random[64] 0.0001ms 0.0001ms 4.3% faster
stdlib_bytes_distance_random[1024] 0.0028ms 0.0028ms 0.4% slower
stdlib_bytes_distance_random[16] 0.0004ms 0.0004ms 1.4% slower
stdlib_bytes_distance_random[64] 0.0005ms 0.0005ms 0.9% slower
stdlib_hex_distance_random[1024] 0.0036ms 0.0036ms 0.3% slower
stdlib_hex_distance_random[16] 0.0004ms 0.0004ms 0.5% faster
stdlib_hex_distance_random[64] 0.0005ms 0.0005ms 1.3% slower

Legend: ✅ Faster (>5%) · ⚠️ Slower (5-30%) · ❌ Regression (>30%) · 🆕 New · 🗑️ Removed

@mrecachinas

Copy link
Copy Markdown
Owner Author

Python/PyO3 overhead context

The M4 Max measurements indicate that tiny operations have largely reached the Python/PyO3 call-overhead floor, approximately 30–40 ns per call. This includes Python dispatch, buffer extraction, and result construction.

Operation Raw Rust Python
3-byte distance ~1 ns 33–40 ns
64-byte distance ~2 ns 34 ns
512×16 first, match at start 1.9 ns 35.6 ns
512×16 first, match at end 402 ns 441 ns
1024×16 random scan 398–524 ns 443–565 ns
1024×32 random scan 798–1,048 ns 837–852 ns

The fixed wrapper cost dominates tiny and immediate-match calls, but becomes negligible for complete scans and larger inputs. Further meaningful Python-side improvements would likely require batching more work per call or changing the API shape rather than continuing to optimize the per-record Rust kernels.

@mrecachinas
mrecachinas merged commit fa1a087 into main Aug 2, 2026
23 checks passed
@mrecachinas
mrecachinas deleted the perf/arm-cross-record-simd branch August 2, 2026 17:24
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