Skip to content

Commit 684a25a

Browse files
mrecachinasCopilot
andauthored
Perf: SIMD + FFI speedups across hex/bytes APIs (#41)
* Wave 2a: PyO3 FFI performance refactor §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 SIMD early-exit for check_hexstrings_within_dist 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> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4d2e7e6 commit 684a25a

13 files changed

Lines changed: 2624 additions & 589 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ python = ["pyo3"]
1515

1616
[dependencies]
1717
pyo3 = { version = "0.25", features = ["extension-module"], optional = true }
18+
rayon = "1.10"
1819

1920
[dev-dependencies]
2021
criterion = "0.5"

benches/hamming_bench.rs

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use criterion::{black_box, criterion_group, criterion_main, Criterion};
22
use hexhamming::{
3-
bytes_hamming_distance, bytes_within_dist,
4-
bytes_array_first_within_dist, bytes_array_best_within_dist, bytes_array_all_within_dist,
5-
hex_hamming_distance, set_algorithm,
3+
bytes_array_all_within_dist, bytes_array_best_within_dist, bytes_array_first_within_dist,
4+
bytes_hamming_distance, bytes_within_dist, hex_hamming_distance, set_algorithm,
65
};
76

87
#[cfg(target_arch = "aarch64")]
@@ -92,13 +91,17 @@ fn bench_array_api(c: &mut Criterion) {
9291

9392
let mut group = c.benchmark_group("array_api/512x16_match_at_0");
9493
group.bench_function("first", |bencher| {
95-
bencher.iter(|| bytes_array_first_within_dist(black_box(&big), black_box(&small), black_box(1)))
94+
bencher.iter(|| {
95+
bytes_array_first_within_dist(black_box(&big), black_box(&small), black_box(1))
96+
})
9697
});
9798
group.bench_function("best", |bencher| {
98-
bencher.iter(|| bytes_array_best_within_dist(black_box(&big), black_box(&small), black_box(1)))
99+
bencher
100+
.iter(|| bytes_array_best_within_dist(black_box(&big), black_box(&small), black_box(1)))
99101
});
100102
group.bench_function("all", |bencher| {
101-
bencher.iter(|| bytes_array_all_within_dist(black_box(&big), black_box(&small), black_box(1)))
103+
bencher
104+
.iter(|| bytes_array_all_within_dist(black_box(&big), black_box(&small), black_box(1)))
102105
});
103106
group.finish();
104107

@@ -108,13 +111,19 @@ fn bench_array_api(c: &mut Criterion) {
108111

109112
let mut group = c.benchmark_group("array_api/512x16_match_at_end");
110113
group.bench_function("first", |bencher| {
111-
bencher.iter(|| bytes_array_first_within_dist(black_box(&big_end), black_box(&small), black_box(1)))
114+
bencher.iter(|| {
115+
bytes_array_first_within_dist(black_box(&big_end), black_box(&small), black_box(1))
116+
})
112117
});
113118
group.bench_function("best", |bencher| {
114-
bencher.iter(|| bytes_array_best_within_dist(black_box(&big_end), black_box(&small), black_box(1)))
119+
bencher.iter(|| {
120+
bytes_array_best_within_dist(black_box(&big_end), black_box(&small), black_box(1))
121+
})
115122
});
116123
group.bench_function("all", |bencher| {
117-
bencher.iter(|| bytes_array_all_within_dist(black_box(&big_end), black_box(&small), black_box(1)))
124+
bencher.iter(|| {
125+
bytes_array_all_within_dist(black_box(&big_end), black_box(&small), black_box(1))
126+
})
118127
});
119128
group.finish();
120129

@@ -128,13 +137,48 @@ fn bench_array_api(c: &mut Criterion) {
128137

129138
let mut group = c.benchmark_group("array_api/16384x64_match_at_mid");
130139
group.bench_function("first", |bencher| {
131-
bencher.iter(|| bytes_array_first_within_dist(black_box(&big_lg), black_box(&small_lg), black_box(1)))
140+
bencher.iter(|| {
141+
bytes_array_first_within_dist(black_box(&big_lg), black_box(&small_lg), black_box(1))
142+
})
132143
});
133144
group.bench_function("best", |bencher| {
134-
bencher.iter(|| bytes_array_best_within_dist(black_box(&big_lg), black_box(&small_lg), black_box(1)))
145+
bencher.iter(|| {
146+
bytes_array_best_within_dist(black_box(&big_lg), black_box(&small_lg), black_box(1))
147+
})
135148
});
136149
group.bench_function("all", |bencher| {
137-
bencher.iter(|| bytes_array_all_within_dist(black_box(&big_lg), black_box(&small_lg), black_box(1)))
150+
bencher.iter(|| {
151+
bytes_array_all_within_dist(black_box(&big_lg), black_box(&small_lg), black_box(1))
152+
})
153+
});
154+
group.finish();
155+
156+
// 100_000 elements of 128 bytes — large batch to showcase parallel speedup
157+
let elem_size_xl = 128usize;
158+
let num_elements_xl = 100_000usize;
159+
let small_xl = vec![0x00u8; elem_size_xl];
160+
let mut big_xl = vec![0x03u8; elem_size_xl * num_elements_xl];
161+
// Scatter matches at various positions
162+
for &idx in &[100, 5_000, 25_000, 50_000, 75_000, 99_999] {
163+
big_xl[idx * elem_size_xl..(idx + 1) * elem_size_xl].copy_from_slice(&small_xl);
164+
}
165+
166+
let mut group = c.benchmark_group("array_api/100000x128_parallel");
167+
group.sample_size(10);
168+
group.bench_function("first", |bencher| {
169+
bencher.iter(|| {
170+
bytes_array_first_within_dist(black_box(&big_xl), black_box(&small_xl), black_box(1))
171+
})
172+
});
173+
group.bench_function("best", |bencher| {
174+
bencher.iter(|| {
175+
bytes_array_best_within_dist(black_box(&big_xl), black_box(&small_xl), black_box(1))
176+
})
177+
});
178+
group.bench_function("all", |bencher| {
179+
bencher.iter(|| {
180+
bytes_array_all_within_dist(black_box(&big_xl), black_box(&small_xl), black_box(1))
181+
})
138182
});
139183
group.finish();
140184
}
@@ -153,7 +197,20 @@ fn bench_hex_string_pack(c: &mut Criterion) {
153197
}
154198

155199
#[cfg(target_arch = "aarch64")]
156-
criterion_group!(benches, bench_hex_by_algo, bench_bytes_by_algo, bench_bytes_within_dist, bench_array_api, bench_hex_string_pack);
200+
criterion_group!(
201+
benches,
202+
bench_hex_by_algo,
203+
bench_bytes_by_algo,
204+
bench_bytes_within_dist,
205+
bench_array_api,
206+
bench_hex_string_pack
207+
);
157208
#[cfg(not(target_arch = "aarch64"))]
158-
criterion_group!(benches, bench_hex_by_algo, bench_bytes_by_algo, bench_bytes_within_dist, bench_array_api);
209+
criterion_group!(
210+
benches,
211+
bench_hex_by_algo,
212+
bench_bytes_by_algo,
213+
bench_bytes_within_dist,
214+
bench_array_api
215+
);
159216
criterion_main!(benches);

0 commit comments

Comments
 (0)