@@ -182,70 +182,81 @@ immutable that is a very slow operation. Use a ``bytearray`` instead, and cast i
182182Benchmark
183183---------
184184
185- All benchmarks on Apple M-series (ARM64) with hexhamming v3.0.0, ``rustc `` 1.85, Python 3.14.
185+ All benchmarks were run on an Apple M4 Max (ARM64, 16 logical cores, 64 GiB)
186+ with hexhamming v3.0.0, ``rustc `` 1.96.1, and Python 3.14.6. Values are the
187+ median of the means from three independent runs.
186188
187189Raw Rust (no Python overhead)
188190~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
189191
190192These numbers show the pure computation time using Rust's ``criterion `` benchmarks
191193(``cargo bench --no-default-features ``), with no Python/PyO3 overhead.
192194
193- =========================================== ===========
194- Name Mean (ns)
195- =========================================== ===========
196- hex_string (NEON) [16 chars] 2.0
197- hex_string (NEON) [64 chars] 5.8
198- hex_string (NEON) [128 chars] 11.2
199- hex_string (NEON) [254 chars] 21.2
200- bytes (NEON) [8 bytes] 1.8
201- bytes (NEON) [32 bytes] 2.8
202- bytes (NEON) [64 bytes] 2.6
203- bytes (NEON) [127 bytes] 6.6
204- bytes_within_dist [127 bytes] 1.6
205- array first [512×16, at start] 2.3
206- array first [512×16, at end] 671.4
207- array best [512×16] 925.8
208- array all [512×16] 799.4
209- =========================================== ===========
210-
211- Larger array workloads cross the parallel (Rayon) threshold; see the Python
212- table below for representative end-to-end numbers.
195+ ================================================ ===========
196+ Name Mean (ns)
197+ ================================================ ===========
198+ hex_string (NEON) [16 chars] 2.4
199+ hex_string (NEON) [64 chars] 8.3
200+ hex_string (NEON) [128 chars] 16.2
201+ hex_string (NEON) [254 chars] 30.2
202+ bytes (native) [8 bytes] 1.7
203+ bytes (native) [32 bytes] 2.4
204+ bytes (native) [64 bytes] 3.2
205+ bytes (native) [127 bytes] 8.4
206+ bytes_within_dist [127 bytes] 2.4
207+ array first [512×16, at start] 6.6
208+ array first [512×16, at end] 1,397.0
209+ array best [512×16, exact at start] 8.3
210+ array best [512×16, exact at end] 1,599.2
211+ array all [512×16] 1,610.1
212+ array best [16384×64, match at mid] 71,121.0
213+ array all [16384×64, match at mid] 79,365.0
214+ array best [100000×128, parallel] 50,996.0
215+ array all [100000×128, parallel] 144,800.0
216+ ================================================ ===========
217+
218+ On AArch64, LLVM's auto-vectorized native byte loop is faster than the
219+ hand-written NEON byte kernel for these sizes, while hexadecimal strings still
220+ use the packed NEON implementation. Large array workloads use four balanced
221+ Rayon jobs to avoid oversubscribing the memory-bound scan.
213222
214223Python API (via PyO3)
215224~~~~~~~~~~~~~~~~~~~~~
216225
217- These numbers include Python function call overhead (~45 ns) using ``pytest-benchmark ``.
226+ These numbers include Python wrapper and function-call overhead using
227+ ``pytest-benchmark ``.
218228
219229====================================================== ===========
220230Name Mean (ns)
221231====================================================== ===========
222- hamming_distance_string [3 chars, same] 84.6
223- hamming_distance_string [3 chars, diff] 83.6
224- hamming_distance_string [64 chars, diff] 91.3
225- hamming_distance_string [1024 chars, diff] 207.4
226- hamming_distance_bytes [3 bytes, same] 127.3
227- hamming_distance_bytes [3 bytes, diff] 96.2
228- hamming_distance_bytes [64 bytes, diff] 169 .9
229- hamming_distance_bytes [1024 bytes, diff] 175.2
230- check_hexstrings_within_dist [1000 chars] 221.3
231- check_bytes_within_dist [16 bytes] 146.2
232- check_bytes_within_dist [64 bytes] 107.7
233- check_bytes_within_dist [127 bytes] 100.6
234- first_within_dist [512×16, at start] 98.7
235- first_within_dist [512×16, mid] 570.8
236- first_within_dist [512×16, at end] 1,030 .1
237- first_within_dist [16384×64, at start] 99.6
238- first_within_dist [16384×64, mid] 20,838.8
239- first_within_dist [16384×64, at end] 41,489.5
240- best_within_dist [512×16, at start] 1,609.8
241- best_within_dist [512×16, at end] 1,116.4
242- best_within_dist [16384×64, mid] 46,826.1
243- all_within_dist [512×16, at start] 1,342.9
244- all_within_dist [512×16, at end] 1,365.9
245- all_within_dist [16384×64, mid] 48,067.2
232+ hamming_distance_string [3 chars, same] 56.3
233+ hamming_distance_string [3 chars, diff] 105.8
234+ hamming_distance_string [64 chars, diff] 60.0
235+ hamming_distance_string [1024 chars, diff] 177.1
236+ hamming_distance_bytes [3 bytes, same] 51.7
237+ hamming_distance_bytes [3 bytes, diff] 51.8
238+ hamming_distance_bytes [64 bytes, diff] 51 .9
239+ hamming_distance_bytes [1024 bytes, diff] 68.9
240+ check_hexstrings_within_dist [1000 chars] 56.4
241+ check_bytes_within_dist [16 bytes] 52.5
242+ check_bytes_within_dist [64 bytes] 51.8
243+ check_bytes_within_dist [127 bytes] 52.8
244+ first_within_dist [512×16, at start] 58.5
245+ first_within_dist [512×16, mid] 771.4
246+ first_within_dist [512×16, at end] 1,475 .1
247+ first_within_dist [16384×64, at start] 160.3
248+ first_within_dist [16384×64, mid] 23,031.5
249+ first_within_dist [16384×64, at end] 45,801.2
250+ best_within_dist [512×16, at start] 75.3
251+ best_within_dist [512×16, at end] 1,703.5
252+ best_within_dist [16384×64, mid] 93,212.8
253+ all_within_dist [512×16, at start] 1,735.5
254+ all_within_dist [512×16, at end] 1,747.3
255+ all_within_dist [16384×64, mid] 93,056.3
246256====================================================== ===========
247257
248- For small inputs, Python call overhead dominates (~45 ns). For large inputs
258+ For small inputs, Python call and wrapper overhead dominates (roughly 40–55 ns
259+ on this machine). For large inputs
249260(1024+ chars, 16384-element arrays), computation dominates and Python overhead
250261is negligible. Array APIs transparently parallelize with Rayon once the input
251262exceeds ~64 KiB; the ``first `` variant additionally short-circuits on the first
0 commit comments