Skip to content

Commit bc5b314

Browse files
committed
Add MD5 benchmark
1 parent 39a19f9 commit bc5b314

File tree

6 files changed

+153
-5
lines changed

6 files changed

+153
-5
lines changed

crates/utils/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ wasm-multithreading = ["unsafe"]
1515

1616
[lints]
1717
workspace = true
18+
19+
[[bench]]
20+
name = "md5"
21+
harness = false

crates/utils/benches/md5.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use std::env::args;
2+
use std::hint::black_box;
3+
use std::io::Write;
4+
use std::time::{Duration, Instant};
5+
use utils::multiversion_test;
6+
7+
#[expect(clippy::print_stdout)]
8+
fn main() {
9+
let mut block_sizes = args()
10+
.skip(1)
11+
.filter(|x| x != "--bench")
12+
.map(|x| x.parse())
13+
.collect::<Result<Vec<usize>, _>>()
14+
.expect("invalid block size(s)");
15+
if block_sizes.is_empty() {
16+
block_sizes.extend([16, 64, 256, 1024, 4096, 16384]);
17+
}
18+
block_sizes.sort_unstable();
19+
let max_block_size = block_sizes.last().unwrap();
20+
21+
println!(" │ MD5 throughput (MiB/s) by block size (B)");
22+
print!(" ");
23+
for &length in &block_sizes {
24+
print!(" │ {length:<6}");
25+
}
26+
println!();
27+
print!("───────────────");
28+
for _ in &block_sizes {
29+
print!("┼────────");
30+
}
31+
println!();
32+
33+
multiversion_test! {
34+
use {utils::simd::*, utils::md5::*};
35+
36+
{
37+
print!("{SIMD_BACKEND:9} {:3}", U32Vector::LANES);
38+
39+
#[expect(clippy::cast_possible_truncation)]
40+
let input = (0..(max_block_size * U32Vector::LANES))
41+
.map(|x| x as u8)
42+
.collect::<Vec<_>>();
43+
44+
for &block_size in &block_sizes {
45+
let input = &input[..block_size * U32Vector::LANES];
46+
47+
let start = Instant::now();
48+
let mut iterations = 0;
49+
let bytes_sec = loop {
50+
for _ in 0..1000 {
51+
let _ = black_box(hash(black_box(input)));
52+
iterations += 1;
53+
}
54+
55+
let elapsed = start.elapsed();
56+
if elapsed > Duration::from_secs(1) {
57+
#[expect(clippy::cast_precision_loss)]
58+
break (iterations * input.len()) as f64 / elapsed.as_secs_f64();
59+
}
60+
};
61+
62+
print!(" │ {:6.0}", (bytes_sec / 1024.0 / 1024.0).round());
63+
let _ = std::io::stdout().flush();
64+
}
65+
66+
println!()
67+
}
68+
}
69+
}

crates/utils/src/multiversion.rs

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ use std::sync::{LazyLock, OnceLock};
4545
/// attribute, which requires functions to be marked as unsafe. A safe fallback version will always
4646
/// be generated.
4747
///
48-
/// See [`crate::md5`] as an example.
48+
/// See [`crate::md5`] as an example. [`multiversion_test!`](crate::multiversion_test!) can be used
49+
/// for testing multiversioned libraries.
4950
#[macro_export]
5051
macro_rules! multiversion {
5152
// One dynamic dispatch function, optionally with extra helper functions.
@@ -223,11 +224,17 @@ macro_rules! multiversion {
223224
(@helper $t:meta $(#[$m:meta])* $v:vis fn $n:ident $t0:tt $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $t11:tt $t12:tt $t13:tt $t14:tt $t15:tt $t16:tt $t17:tt $t18:tt $t19:tt $t20:tt $t21:tt $t22:tt $t23:tt $t24:tt $t25:tt $t26:tt $t27:tt $t28:tt $t29:tt $t30:tt $t31:tt $b:block $($tail:tt)*) => {$(#[$m])* #[$t] $v unsafe fn $n $t0 $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10 $t11 $t12 $t13 $t14 $t15 $t16 $t17 $t18 $t19 $t20 $t21 $t22 $t23 $t24 $t25 $t26 $t27 $t28 $t29 $t30 $t31 { #[allow(clippy::allow_attributes, clippy::macro_metavars_in_unsafe, unused_unsafe)] unsafe { $b } } $crate::multiversion!{@helper $t $($tail)*}};
224225
}
225226

226-
/// Helper for testing [`multiversion!`] library functions.
227+
/// Helper for testing and benchmarking [`multiversion!`] library functions.
227228
///
228-
/// `#[target_feature(...)]` isn't applied to the test functions as the feature-specific code should
229+
/// The first rule is for testing and creates individual
230+
/// [`#[test]`](https://doc.rust-lang.org/reference/attributes/testing.html) functions for each
231+
/// implementation.
232+
///
233+
/// The second rule is more general, duplicating the same expression for each implementation and
234+
/// is useful for benchmarking.
235+
///
236+
/// `#[target_feature(...)]` isn't applied to the test code as the feature-specific code should
229237
/// be elsewhere, inside a [`multiversion!`] macro.
230-
#[cfg(test)]
231238
#[macro_export]
232239
macro_rules! multiversion_test {
233240
(
@@ -286,7 +293,7 @@ macro_rules! multiversion_test {
286293
#[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
287294
use {$($($path::)+avx2::*),*};
288295

289-
if !std::arch::is_x86_feature_detected!("avx2") {
296+
if !$crate::multiversion::Version::AVX2.supported() {
290297
use std::io::{stdout, Write};
291298
let _ = writeln!(&mut stdout(), "warning: skipping test in {}::avx2 due to missing avx2 support", module_path!());
292299
return;
@@ -295,6 +302,59 @@ macro_rules! multiversion_test {
295302
unsafe { $body }
296303
}
297304
};
305+
306+
(
307+
use {$($($path:ident::)+*),*};
308+
309+
{ $($tail:tt)+ }
310+
) => {
311+
#[allow(
312+
clippy::reversed_empty_ranges,
313+
clippy::range_plus_one,
314+
clippy::modulo_one,
315+
clippy::trivially_copy_pass_by_ref
316+
)]
317+
{
318+
#[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
319+
use {$($($path::)+scalar::*),*};
320+
321+
$crate::multiversion_test!(@expr { $($tail)+ });
322+
}
323+
324+
{
325+
#[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
326+
use {$($($path::)+array128::*),*};
327+
328+
$crate::multiversion_test!(@expr { $($tail)+ });
329+
}
330+
331+
{
332+
#[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
333+
use {$($($path::)+array256::*),*};
334+
335+
$crate::multiversion_test!(@expr { $($tail)+ });
336+
}
337+
338+
#[cfg(not(target_family = "wasm"))]
339+
#[allow(clippy::large_types_passed_by_value)]
340+
{
341+
#[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
342+
use {$($($path::)+array4096::*),*};
343+
344+
$crate::multiversion_test!(@expr { $($tail)+ });
345+
}
346+
347+
#[cfg(all(feature="unsafe", any(target_arch = "x86", target_arch = "x86_64")))]
348+
if $crate::multiversion::Version::AVX2.supported() {
349+
unsafe {
350+
#[allow(clippy::allow_attributes, unused_imports, clippy::wildcard_imports)]
351+
use {$($($path::)+avx2::*),*};
352+
353+
$crate::multiversion_test!(@expr { $($tail)+ });
354+
}
355+
}
356+
};
357+
(@expr $e:expr) => { $e }
298358
}
299359

300360
macro_rules! versions_impl {

crates/utils/src/simd/array.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,27 @@ impl<const N: usize> U32Vector<N> {
9696

9797
/// 128-bit wide vector implementations using arrays.
9898
pub mod array128 {
99+
/// The name of this backend.
100+
pub const SIMD_BACKEND: &str = "array128";
101+
99102
/// Array vector with four [u32] lanes.
100103
pub type U32Vector = super::U32Vector<4>;
101104
}
102105

103106
/// 256-bit wide vector implementations using arrays.
104107
pub mod array256 {
108+
/// The name of this backend.
109+
pub const SIMD_BACKEND: &str = "array256";
110+
105111
/// Array vector with eight [u32] lanes.
106112
pub type U32Vector = super::U32Vector<8>;
107113
}
108114

109115
/// 4096-bit wide vector implementations using arrays.
110116
pub mod array4096 {
117+
/// The name of this backend.
118+
pub const SIMD_BACKEND: &str = "array4096";
119+
111120
/// Array vector with 128 [u32] lanes.
112121
pub type U32Vector = super::U32Vector<128>;
113122
}

crates/utils/src/simd/avx2.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ use std::arch::x86_64::*;
1010
#[allow(clippy::allow_attributes, clippy::wildcard_imports)]
1111
use std::arch::x86::*;
1212

13+
/// The name of this backend.
14+
pub const SIMD_BACKEND: &str = "avx2";
15+
1316
/// AVX2 vector with eight [u32] lanes.
1417
#[derive(Clone, Copy)]
1518
#[repr(transparent)]

crates/utils/src/simd/scalar.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
use std::ops::{Add, BitAnd, BitOr, BitXor, Not};
77

8+
/// The name of this backend.
9+
pub const SIMD_BACKEND: &str = "scalar";
10+
811
/// Scalar vector with a single [u32] lane.
912
#[derive(Clone, Copy)]
1013
#[repr(transparent)]

0 commit comments

Comments
 (0)