Skip to content

Commit 38420de

Browse files
mrecachinasCopilot
andcommitted
docs: refresh M4 Max performance benchmarks
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e7b02886-a2c5-43cd-bb97-bfa037d3b625
1 parent d2a50cb commit 38420de

1 file changed

Lines changed: 58 additions & 47 deletions

File tree

README.rst

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -182,70 +182,81 @@ immutable that is a very slow operation. Use a ``bytearray`` instead, and cast i
182182
Benchmark
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

187189
Raw Rust (no Python overhead)
188190
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
189191

190192
These 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

214223
Python 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
====================================================== ===========
220230
Name 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
250261
is negligible. Array APIs transparently parallelize with Rayon once the input
251262
exceeds ~64 KiB; the ``first`` variant additionally short-circuits on the first

0 commit comments

Comments
 (0)