diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0a019cb..498ddfd 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -70,4 +70,4 @@ jobs: - uses: actions/checkout@v4 - run: rustup update stable && rustup default stable - run: rustup component add clippy - - run: cargo clippy --workspace --all-targets --no-deps + - run: cargo clippy --lib --bins --tests --examples --no-deps diff --git a/Cargo.toml b/Cargo.toml index 43e021c..8efc783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ default = ["std"] std = [] nightly = [] rand = ["dep:rand", "std"] +std_bench = ["nightly"] [dependencies] rand = { version = "0.8", optional = true } diff --git a/README.md b/README.md index d93cf46..32da2ae 100644 --- a/README.md +++ b/README.md @@ -40,3 +40,56 @@ It can be turned off in `Cargo.toml` like so: ```toml rustc-hash = { version = "2.0", default-features = false } ``` + +## Benchmarks + +The benchmarks are run with the following command: + +- `rustc-hash::FxHashMap` + +```console +> cargo +nightly bench + +test fx_benchmarks::bench_hashmap_create ... bench: 2.08 ns/iter (+/- 0.04) +test fx_benchmarks::bench_hashmap_insert ... bench: 175.02 ns/iter (+/- 5.89) +test fx_benchmarks::bench_hashmap_insert_large_data ... bench: 16,121,283.40 ns/iter (+/- 2,276,733.63) +test fx_benchmarks::bench_hashmap_iter ... bench: 2.05 ns/iter (+/- 0.07) +test fx_benchmarks::bench_hashmap_iter_large_data ... bench: 173,197.71 ns/iter (+/- 3,914.19) +test fx_benchmarks::bench_hashmap_lookup ... bench: 2.44 ns/iter (+/- 0.11) +test fx_benchmarks::bench_hashmap_lookup_large_data ... bench: 444,745.90 ns/iter (+/- 91,353.67) +test fx_benchmarks::bench_hashmap_reinsert ... bench: 67.53 ns/iter (+/- 5.21) +test fx_benchmarks::bench_hashmap_reinsert_large_data ... bench: 12,455,191.70 ns/iter (+/- 9,219,748.08) +test fx_benchmarks::bench_hashmap_remove ... bench: 4.95 ns/iter (+/- 0.17) +test fx_benchmarks::bench_hashmap_remove_large_data ... bench: 561,822.95 ns/iter (+/- 14,508.17) +test fx_benchmarks::bench_hashmap_with_mutex ... bench: 77.26 ns/iter (+/- 15.62) +test fx_benchmarks::bench_hashmap_with_mutex_large_data ... bench: 13,674,849.90 ns/iter (+/- 9,925,902.12) +test fx_benchmarks::bench_hashmap_with_rwlock ... bench: 73.16 ns/iter (+/- 2.48) +test fx_benchmarks::bench_hashmap_with_rwlock_large_data ... bench: 12,159,066.70 ns/iter (+/- 9,738,272.64) + +test result: ok. 0 passed; 0 failed; 0 ignored; 15 measured; 0 filtered out; finished in 37.61s +``` + +- `std::collections::HashMap` + +```console +> cargo +nightly bench --features=std-bench + +... +test std_benchmarks::bench_hashmap_create ... bench: 9.20 ns/iter (+/- 0.14) +test std_benchmarks::bench_hashmap_insert ... bench: 195.85 ns/iter (+/- 6.37) +test std_benchmarks::bench_hashmap_insert_large_data ... bench: 21,693,487.50 ns/iter (+/- 3,609,774.56) +test std_benchmarks::bench_hashmap_iter ... bench: 2.08 ns/iter (+/- 0.05) +test std_benchmarks::bench_hashmap_iter_large_data ... bench: 226,075.13 ns/iter (+/- 9,295.55) +test std_benchmarks::bench_hashmap_lookup ... bench: 16.44 ns/iter (+/- 0.37) +test std_benchmarks::bench_hashmap_lookup_large_data ... bench: 2,456,185.40 ns/iter (+/- 109,599.74) +test std_benchmarks::bench_hashmap_reinsert ... bench: 84.26 ns/iter (+/- 2.26) +test std_benchmarks::bench_hashmap_reinsert_large_data ... bench: 10,378,541.70 ns/iter (+/- 5,037,469.76) +test std_benchmarks::bench_hashmap_remove ... bench: 17.83 ns/iter (+/- 0.13) +test std_benchmarks::bench_hashmap_remove_large_data ... bench: 3,100,606.20 ns/iter (+/- 28,839.83) +test std_benchmarks::bench_hashmap_with_mutex ... bench: 96.03 ns/iter (+/- 4.22) +test std_benchmarks::bench_hashmap_with_mutex_large_data ... bench: 11,495,400.00 ns/iter (+/- 4,018,753.31) +test std_benchmarks::bench_hashmap_with_rwlock ... bench: 89.40 ns/iter (+/- 3.91) +test std_benchmarks::bench_hashmap_with_rwlock_large_data ... bench: 12,300,512.40 ns/iter (+/- 11,790,539.19) + +test result: ok. 0 passed; 0 failed; 0 ignored; 15 measured; 0 filtered out; finished in 42.77s +``` diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs new file mode 100644 index 0000000..c6850ea --- /dev/null +++ b/benches/benchmarks.rs @@ -0,0 +1,197 @@ +#![cfg(feature = "nightly")] +#![feature(test)] + +extern crate test; + +use std::sync::{Arc, Mutex, RwLock}; +use test::{black_box, Bencher}; + +#[cfg(not(feature = "std_bench"))] +use rustc_hash::FxHashMap; + +#[cfg(feature = "std_bench")] +use std::collections::HashMap; + +const LARGE_ITEM_SIZE: usize = 100000; + +macro_rules! benchmarks { + ($map_type:ty,$map_creater:expr) => { + // Benchmark for creating a FxHashMap. + #[bench] + fn bench_hashmap_create(b: &mut Bencher) { + b.iter(|| { + let map: $map_type = $map_creater(); + black_box(map) + }); + } + + // Benchmark for inserting items into a FxHashMap. + #[bench] + fn bench_hashmap_insert(b: &mut Bencher) { + b.iter(|| { + let mut map = $map_creater(); + map.insert(1, 1.to_string()); + black_box(map) + }); + } + + #[bench] + fn bench_hashmap_insert_large_data(b: &mut Bencher) { + b.iter(|| { + let mut map = $map_creater(); + for i in 0..LARGE_ITEM_SIZE { + map.insert(i, i.to_string()); + } + black_box(map) + }); + } + + // Benchmark for looking up items in a FxHashMap. + #[bench] + fn bench_hashmap_lookup(b: &mut Bencher) { + let mut map = $map_creater(); + map.insert(1, 1.to_string()); + b.iter(|| { + black_box(map.get(&1)); + }); + } + + #[bench] + fn bench_hashmap_lookup_large_data(b: &mut Bencher) { + let mut map = $map_creater(); + for i in 0..LARGE_ITEM_SIZE { + map.insert(i, i.to_string()); + } + b.iter(|| { + for i in 0..LARGE_ITEM_SIZE { + black_box(map.get(&i)); + } + }); + } + + // Benchmark for removing items from a FxHashMap. + #[bench] + fn bench_hashmap_remove(b: &mut Bencher) { + let mut map = $map_creater(); + map.insert(1, 1.to_string()); + b.iter(|| { + black_box(map.remove(&1)); + }); + } + + #[bench] + fn bench_hashmap_remove_large_data(b: &mut Bencher) { + let mut map = $map_creater(); + for i in 0..LARGE_ITEM_SIZE { + map.insert(i, i.to_string()); + } + b.iter(|| { + for i in 0..LARGE_ITEM_SIZE { + black_box(map.remove(&i)); + } + }); + } + + // Benchmark for iterating over a FxHashMap. + #[bench] + fn bench_hashmap_iter(b: &mut Bencher) { + let mut map = $map_creater(); + map.insert(1, 1.to_string()); + b.iter(|| { + for (k, v) in &map { + black_box((k, v)); + } + }); + } + + #[bench] + fn bench_hashmap_iter_large_data(b: &mut Bencher) { + let mut map = $map_creater(); + for i in 0..LARGE_ITEM_SIZE { + map.insert(i, i.to_string()); + } + b.iter(|| { + for (k, v) in &map { + black_box((k, v)); + } + }); + } + + // Benchmark for reinserting items into a FxHashMap. + #[bench] + fn bench_hashmap_reinsert(b: &mut Bencher) { + let mut map = $map_creater(); + map.insert(1, 1.to_string()); + b.iter(|| { + map.insert(1, 2.to_string()); + }); + } + + #[bench] + fn bench_hashmap_reinsert_large_data(b: &mut Bencher) { + let mut map = $map_creater(); + for i in 0..LARGE_ITEM_SIZE { + map.insert(i, i.to_string()); + } + b.iter(|| { + for i in 0..LARGE_ITEM_SIZE { + map.insert(i, (i + 1).to_string()); + } + }); + } + + // Benchmark for inserting items into a FxHashMap with a Mutex. + #[bench] + fn bench_hashmap_with_mutex(b: &mut Bencher) { + let map = Arc::new(Mutex::new($map_creater())); + b.iter(|| { + let mut locked_map = map.lock().unwrap(); + locked_map.insert(1, 1.to_string()); + }); + } + + #[bench] + fn bench_hashmap_with_mutex_large_data(b: &mut Bencher) { + let map = Arc::new(Mutex::new($map_creater())); + b.iter(|| { + for i in 0..LARGE_ITEM_SIZE { + let mut locked_map = map.lock().unwrap(); + locked_map.insert(i, i.to_string()); + } + }); + } + + // Benchmark for inserting items into a FxHashMap with a RwLock. + #[bench] + fn bench_hashmap_with_rwlock(b: &mut Bencher) { + let map = Arc::new(RwLock::new($map_creater())); + b.iter(|| { + let mut locked_map = map.write().unwrap(); + locked_map.insert(1, 1.to_string()); + }); + } + + #[bench] + fn bench_hashmap_with_rwlock_large_data(b: &mut Bencher) { + let map = Arc::new(RwLock::new($map_creater())); + b.iter(|| { + for i in 0..LARGE_ITEM_SIZE { + let mut locked_map = map.write().unwrap(); + locked_map.insert(i, i.to_string()); + } + }); + } + }; +} + +#[cfg(not(feature = "std_bench"))] +mod fx_benchmarks { + use super::*; + benchmarks!(FxHashMap, || FxHashMap::default()); +} + +#[cfg(feature = "std_bench")] +mod std_benchmarks { + use super::*; + benchmarks!(HashMap,|| HashMap::new()); +}