diff --git a/Cargo.lock b/Cargo.lock index fd2f9d0f6..bc15956fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5645,9 +5645,9 @@ dependencies = [ [[package]] name = "lazy-regex" -version = "3.6.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496" +checksum = "4994ba703f78b083e2f7946dac9251abd83fd43a0365f030e99b69be5b4b9ef9" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -5656,9 +5656,9 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "3.6.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" +checksum = "fd97232314824e6dbef1918a871bb93f51070455e3715bf26e19a6d01aa977a0" dependencies = [ "proc-macro2", "quote", @@ -10131,6 +10131,7 @@ dependencies = [ "bytes", "convert_case 0.11.0", "crc-fast", + "criterion", "flate2", "futures", "hex-simd", diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 4ee08d7b8..5ba901965 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -58,11 +58,16 @@ url = { workspace = true, optional = true } zstd = { workspace = true, optional = true } [dev-dependencies] +criterion = { workspace = true, features = ["html_reports"] } tempfile = { workspace = true } tokio = { workspace = true, features = ["macros", "rt"] } temp-env = { workspace = true } proptest = "1" +[[bench]] +name = "hash_hotpath_benchmark" +harness = false + [target.'cfg(windows)'.dependencies] windows = { workspace = true, optional = true, features = ["Win32_Storage_FileSystem"] } diff --git a/crates/utils/benches/hash_hotpath_benchmark.rs b/crates/utils/benches/hash_hotpath_benchmark.rs new file mode 100644 index 000000000..2fe06f6ae --- /dev/null +++ b/crates/utils/benches/hash_hotpath_benchmark.rs @@ -0,0 +1,55 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use rustfs_utils::HashAlgorithm; +use std::hint::black_box; + +fn generate_payload(size: usize) -> Vec { + (0..size) + .map(|i| u8::try_from(i % 251).expect("modulo output fits in u8")) + .collect() +} + +fn bench_hash_hotpaths(c: &mut Criterion) { + let payloads = [ + ("64KiB", generate_payload(64 * 1024)), + ("1MiB", generate_payload(1024 * 1024)), + ]; + let algorithms = [ + ("md5", HashAlgorithm::Md5), + ("sha256", HashAlgorithm::SHA256), + ("highwayhash256s", HashAlgorithm::HighwayHash256S), + ("highwayhash256s_legacy", HashAlgorithm::HighwayHash256SLegacy), + ]; + + let mut group = c.benchmark_group("hash_hotpath"); + for (payload_name, payload) in &payloads { + let payload_len = u64::try_from(payload.len()).expect("benchmark payload length fits in u64"); + group.throughput(Throughput::Bytes(payload_len)); + for (algo_name, algorithm) in &algorithms { + let algorithm = algorithm.clone(); + group.bench_with_input(BenchmarkId::new(*algo_name, payload_name), payload.as_slice(), move |b, payload| { + b.iter(|| { + let hash = algorithm.hash_encode(black_box(payload)); + black_box(hash.as_ref()[0]); + }); + }); + } + } + group.finish(); +} + +criterion_group!(benches, bench_hash_hotpaths); +criterion_main!(benches); diff --git a/crates/utils/src/hash.rs b/crates/utils/src/hash.rs index 367b0e194..b2f6f4eba 100644 --- a/crates/utils/src/hash.rs +++ b/crates/utils/src/hash.rs @@ -29,14 +29,24 @@ const LEGACY_HIGHWAY_HASH256_KEY: [u8; 32] = [ 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, ]; -fn highway_key_from_bytes(bytes: &[u8; 32]) -> [u64; 4] { - let mut key = [0u64; 4]; - for (i, chunk) in bytes.chunks_exact(8).enumerate() { - key[i] = u64::from_le_bytes(chunk.try_into().unwrap()); - } - key +const fn highway_key_from_bytes(bytes: &[u8; 32]) -> [u64; 4] { + [ + u64::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]]), + u64::from_le_bytes([ + bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], + ]), + u64::from_le_bytes([ + bytes[16], bytes[17], bytes[18], bytes[19], bytes[20], bytes[21], bytes[22], bytes[23], + ]), + u64::from_le_bytes([ + bytes[24], bytes[25], bytes[26], bytes[27], bytes[28], bytes[29], bytes[30], bytes[31], + ]), + ] } +const MAGIC_HIGHWAY_HASH256_PARSED_KEY: Key = Key(highway_key_from_bytes(&MAGIC_HIGHWAY_HASH256_KEY)); +const LEGACY_HIGHWAY_HASH256_PARSED_KEY: Key = Key(highway_key_from_bytes(&LEGACY_HIGHWAY_HASH256_KEY)); + #[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone, Eq, Hash)] /// Supported hash algorithms for bitrot protection. pub enum HashAlgorithm { @@ -100,25 +110,23 @@ impl HashAlgorithm { /// # Returns /// A byte slice containing the hash of the input data /// + #[inline] pub fn hash_encode(&self, data: &[u8]) -> impl AsRef<[u8]> { match self { HashAlgorithm::Md5 => HashEncoded::Md5(Md5::digest(data).into()), HashAlgorithm::HighwayHash256 => { - let key = Key(highway_key_from_bytes(&MAGIC_HIGHWAY_HASH256_KEY)); - let mut hasher = HighwayHasher::new(key); + let mut hasher = HighwayHasher::new(MAGIC_HIGHWAY_HASH256_PARSED_KEY); hasher.append(data); HashEncoded::HighwayHash256(u8x32_from_u64x4(hasher.finalize256())) } HashAlgorithm::SHA256 => HashEncoded::Sha256(Sha256::digest(data).into()), HashAlgorithm::HighwayHash256S => { - let key = Key(highway_key_from_bytes(&MAGIC_HIGHWAY_HASH256_KEY)); - let mut hasher = HighwayHasher::new(key); + let mut hasher = HighwayHasher::new(MAGIC_HIGHWAY_HASH256_PARSED_KEY); hasher.append(data); HashEncoded::HighwayHash256S(u8x32_from_u64x4(hasher.finalize256())) } HashAlgorithm::HighwayHash256SLegacy => { - let key = Key(highway_key_from_bytes(&LEGACY_HIGHWAY_HASH256_KEY)); - let mut hasher = HighwayHasher::new(key); + let mut hasher = HighwayHasher::new(LEGACY_HIGHWAY_HASH256_PARSED_KEY); hasher.append(data); HashEncoded::HighwayHash256SLegacy(u8x32_from_u64x4(hasher.finalize256())) } diff --git a/scripts/run_hotpath_warp_ab.sh b/scripts/run_hotpath_warp_ab.sh index 27edb1f19..635835227 100755 --- a/scripts/run_hotpath_warp_ab.sh +++ b/scripts/run_hotpath_warp_ab.sh @@ -337,7 +337,7 @@ measure() { ) [[ -n "$COOLDOWN_SECS" ]] && args+=(--cooldown-secs "$COOLDOWN_SECS") [[ -n "$baseline_csv" ]] && args+=(--baseline-csv "$baseline_csv") - run "$ENHANCED_BENCH" "${args[@]}" + run "$ENHANCED_BENCH" "${args[@]}" >&2 echo "$cell" }