Skip to content

Perf: SIMD + FFI speedups across hex/bytes APIs - #41

Merged
mrecachinas merged 2 commits into
masterfrom
perf/simd-ffi-speedup
Apr 16, 2026
Merged

Perf: SIMD + FFI speedups across hex/bytes APIs#41
mrecachinas merged 2 commits into
masterfrom
perf/simd-ffi-speedup

Conversation

@mrecachinas

Copy link
Copy Markdown
Owner

Summary

Round of performance work across the Rust SIMD kernels and the PyO3 FFI layer. Measured on Apple M-series (NEON); x86 kernels updated in lockstep.

Highlights (measured)

API Before After Speedup
bytes_arrays_all_within_dist (10k × 128 B) 0.186 ms 0.050 ms 3.7×
check_bytes_within_dist (4 KB) 0.284 µs 0.155 µs 1.83×
hamming_distance_bytes (1 KB) 0.227 µs 0.164 µs 1.38×
check_hexstrings_within_dist (1024 ch, similar) 0.75 µs 0.19 µs
check_hexstrings_within_dist (1024 ch, random, tight max) 0.095 µs 0.072 µs 1.3×

memoryview / bytearray / NumPy buffers are now accepted zero-copy.

What's in here

SIMD kernels (src/neon_simd.rs, src/x86_simd.rs, src/native.rs)

  • New dedicated NEON byte kernel (previously fell through to native).
  • AVX2 hex-string path switched to VPSHUFB nibble-LUT popcount (no more pack → GPR popcnt).
  • Unrolled early-exit paths in SSE / AVX2 / AVX-512 (256 B / 512 B / 1024 B per SAD + threshold check).
  • Consolidated redundant CMP ops; hoisted the popcount table out of hot loops.
  • Dispatch contract: bytes kernels now return distance or u64::MAX sentinel when max_dist is exceeded (kills the old 2-pass pattern in batch helpers).
  • New _with_max variants for all 4 SIMD hex-string kernels + dispatcher, with periodic in-loop threshold checks so SIMD gets the same early-exit benefit the scalar path had.

FFI layer (src/python.rs)

  • check_hexstrings_within_dist now routes ≥64-char inputs through the SIMD dispatcher (previously always scalar).
  • Zero-copy PyBuffer for bytes APIs — accepts bytes, bytearray, memoryview, NumPy arrays without copying.
  • Direct-typed parameters, with_capacity on result vecs, dedup/cleanup in set_algo and friends.

Batch APIs (src/api.rs)

  • Rayon parallelization for first_within_dist / best_within_dist / all_within_dist behind a 64 KB payload gate. Tie-breaking (lowest index) preserved via fold + reduce. Serial fallbacks retained and tested equivalent.

Tests

  • 30+ new tests (Rust + Python): SIMD length boundaries, invalid-char validation on SIMD paths, tie-break ordering, serial-vs-parallel equivalence, memoryview / bytearray / NumPy inputs, set_algo behavior, early-exit correctness.
  • All 122 Python + 71 Rust tests pass. cargo fmt, ruff clean.

Notes

An initial attempt to gate SIMD vs. scalar on max_dist >= len was a trap: the right choice depends on the actual distance, not a length heuristic. Pushing periodic threshold checks into the SIMD kernels themselves is the clean answer — it wins similar-string similarity search (common case) and random + tight-threshold rejection (previously scalar's strength).

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

mrecachinas and others added 2 commits April 16, 2026 13:36
§1 Wire SIMD into check_hexstrings_within_dist for len >= 64

§5 Zero-copy PyBuffer for all byte-input functions

§9 set_algo delegates to api::set_algorithm

§10 Direct typed params in pyfunction signatures

§12 Vec::with_capacity in check_bytes_arrays_all_within_dist

§14 Consolidate python.rs thin wrappers over api dispatch

17 new tests for buffer-protocol, SIMD-path, set_algo.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add _with_max variants to all SIMD hex string distance functions
(NEON, SSE4.1, AVX2, AVX-512) that check accumulated distance
against max_dist after each iteration and return u64::MAX sentinel
when exceeded. This restores early-exit performance for the
random+tight workload without regressing the similar-strings case.

New functions:
- neon_simd::hamming_distance_string_neon_pack_with_max
- x86_simd::hamming_distance_string_sse_with_max
- x86_simd::hamming_distance_string_avx2_with_max
- x86_simd::hamming_distance_string_avx512_with_max
- lib::hamming_distance_string_dispatch_with_max

Performance (1024 hex chars):
- random, max=100:  0.192 us -> 0.072 us (2.7x faster, beats baseline)
- similar, max=10:  0.193 us -> 0.183 us (no regression)
- similar, max=100: 0.195 us -> 0.183 us (no regression)

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

Copy link
Copy Markdown

📊 Benchmark Comparison Results

Status Benchmark Base PR Delta
check_bytes_arrays_first_within_dist[16384 elems,s=64,at 0] 0.0358ms 0.0002ms 99.5% faster
check_bytes_arrays_first_within_dist[16384 elems,s=64,mid] 0.0880ms 0.0440ms 50.0% faster
check_bytes_arrays_all_within_dist[16384 elems,s=64,at 0] 0.1602ms 0.0880ms 45.1% faster
check_bytes_arrays_all_within_dist[16384 elems,s=64,mid] 0.1584ms 0.0892ms 43.7% faster
check_bytes_arrays_all_within_dist[16384 elems,s=64,end] 0.1578ms 0.0891ms 43.5% faster
check_bytes_arrays_best_within_dist[ 512 elems,s=16,at 0] 0.0085ms 0.0048ms 43.3% faster
check_bytes_arrays_best_within_dist[16384 elems,s=64,mid] 0.1530ms 0.0913ms 40.4% faster
check_bytes_arrays_best_within_dist[16384 elems,s=64,at 0] 0.1551ms 0.0926ms 40.3% faster
check_bytes_arrays_best_within_dist[16384 elems,s=64,end] 0.1529ms 0.0923ms 39.7% faster
check_bytes_arrays_first_within_dist[16384 elems,s=64,end] 0.1426ms 0.0883ms 38.1% faster
check_bytes_arrays_best_within_dist[ 512 elems,s=16,mid] 0.0062ms 0.0045ms 27.1% faster
➖ 34 benchmarks within noise (click to expand)
Benchmark Base PR Delta
check_bytes_arrays_all_within_dist[ 512 elems,s=16,at 0] 0.0040ms 0.0039ms 2.1% faster
check_bytes_arrays_all_within_dist[ 512 elems,s=16,end] 0.0040ms 0.0039ms 2.1% faster
check_bytes_arrays_all_within_dist[ 512 elems,s=16,mid] 0.0040ms 0.0039ms 2.5% faster
check_bytes_arrays_all_within_dist[ 1024 elems,s=32,at 0] 0.0087ms 0.0084ms 3.1% faster
check_bytes_arrays_all_within_dist[ 1024 elems,s=32,end] 0.0088ms 0.0085ms 3.4% faster
check_bytes_arrays_all_within_dist[ 1024 elems,s=32,mid] 0.0088ms 0.0085ms 3.4% faster
check_bytes_arrays_best_within_dist[ 512 elems,s=16,end] 0.0038ms 0.0041ms 6.7% slower
check_bytes_arrays_best_within_dist[ 1024 elems,s=32,at 0] 0.0077ms 0.0087ms 12.5% slower
check_bytes_arrays_best_within_dist[ 1024 elems,s=32,end] 0.0083ms 0.0087ms 4.6% slower
check_bytes_arrays_best_within_dist[ 1024 elems,s=32,mid] 0.0080ms 0.0087ms 8.7% slower
check_bytes_arrays_first_within_dist[ 512 elems,s=16,at 0] 0.0004ms 0.0002ms 51.0% faster
check_bytes_arrays_first_within_dist[ 512 elems,s=16,end] 0.0034ms 0.0038ms 9.9% slower
check_bytes_arrays_first_within_dist[ 512 elems,s=16,mid] 0.0019ms 0.0020ms 8.5% slower
check_bytes_arrays_first_within_dist[ 1024 elems,s=32,at 0] 0.0010ms 0.0002ms 81.1% faster
check_bytes_arrays_first_within_dist[ 1024 elems,s=32,end] 0.0077ms 0.0083ms 7.9% slower
check_bytes_arrays_first_within_dist[ 1024 elems,s=32,mid] 0.0043ms 0.0043ms 1.0% slower
check_bytes_within_dist[127 bytes,d=500] 0.0002ms 0.0003ms 58.8% slower
check_bytes_within_dist[16 bytes,d=0] 0.0003ms 0.0003ms 7.3% faster
check_bytes_within_dist[64 bytes,d=100] 0.0002ms 0.0003ms 64.7% slower
hex_within_dist 0.0001ms 0.0001ms 19.3% faster
hd_bytes[1000-diff] 0.0002ms 0.0002ms 3.7% slower
hd_bytes[1000-same] 0.0002ms 0.0002ms 9.6% slower
hd_bytes[1024-diff] 0.0002ms 0.0002ms 5.1% slower
hd_bytes[1024-same] 0.0002ms 0.0002ms 6.2% slower
hd_bytes[3-diff] 0.0002ms 0.0002ms 18.6% slower
hd_bytes[3-same] 0.0001ms 0.0002ms 18.4% slower
hd_bytes[64-diff] 0.0002ms 0.0002ms 16.1% slower
hd_string[1000-diff] 0.0003ms 0.0002ms 25.4% faster
hd_string[1000-same] 0.0003ms 0.0002ms 25.4% faster
hd_string[1024-diff] 0.0003ms 0.0002ms 25.7% faster
hd_string[1024-same] 0.0003ms 0.0002ms 20.9% faster
hd_string[3-diff] 0.0003ms 0.0003ms 3.6% slower
hd_string[3-same] 0.0002ms 0.0001ms 3.6% faster
hd_string[64-diff] 0.0002ms 0.0001ms 8.8% faster

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

@mrecachinas
mrecachinas merged commit 684a25a into master Apr 16, 2026
23 checks passed
@mrecachinas
mrecachinas deleted the perf/simd-ffi-speedup branch April 16, 2026 19:00
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