#![feature(test)] extern crate test; use odht::{Config, FxHashFn, HashTable, HashTableOwned}; use rustc_hash::FxHashMap; #[repr(C)] #[derive(Copy, Clone, Hash, Eq, PartialEq)] struct TestKey(u64, u64); struct FxConfig; impl Config for FxConfig { type Key = TestKey; type Value = u32; type EncodedKey = [u8; 16]; type EncodedValue = [u8; 4]; type H = FxHashFn; #[inline] fn encode_key(k: &Self::Key) -> Self::EncodedKey { let mut result = [0u8; 16]; result[0..8].copy_from_slice(&k.0.to_le_bytes()); result[8..16].copy_from_slice(&k.1.to_le_bytes()); result } #[inline] fn encode_value(v: &Self::Value) -> Self::EncodedValue { v.to_le_bytes() } #[inline] fn decode_key(_k: &Self::EncodedKey) -> Self::Key { panic!() } #[inline] fn decode_value(v: &Self::EncodedValue) -> Self::Value { u32::from_le_bytes(*v) } } fn index_contained(i: usize) -> bool { i % 10 != 3 } // Call functions being benchmark through an #[inline(never)] wrapper // so that we get a clear inlining barrier to look at in profilers. #[inline(never)] fn get_value Option>(f: &F, key: &TestKey) -> Option { f(key) } #[inline(never)] fn insert_value Option>( f: &mut F, key: TestKey, value: u32, ) -> Option { f(key, value) } fn generate_hash_table( test_data: &[(TestKey, u32)], load_factor_percent: u8, ) -> HashTableOwned { let values: Vec<_> = test_data .iter() .enumerate() .filter(|&(i, _)| index_contained(i)) .map(|(_, x)| x) .collect(); let mut table = HashTableOwned::with_capacity(values.len(), load_factor_percent); for (key, value) in values { table.insert(key, value); } table } fn generate_std_hash_table(test_data: &[(TestKey, u32)]) -> FxHashMap { let mut table = FxHashMap::default(); let values: Vec<_> = test_data .iter() .enumerate() .filter(|&(i, _)| index_contained(i)) .map(|(_, x)| *x) .collect(); for (key, value) in values { table.insert(key, value); } table } fn generate_test_data(num_values: usize) -> Vec<(TestKey, u32)> { use rand::prelude::*; (0..num_values) .map(|_| (TestKey(random(), random()), random())) .collect() } const LOOKUP_ITERATIONS: usize = 10; const INSERT_ITERATIONS: usize = 10; fn bench_odht_fx_lookup(b: &mut test::Bencher, num_values: usize, load_factor_percent: u8) { let test_data = crate::generate_test_data(num_values); let table = crate::generate_hash_table(&test_data, load_factor_percent); let mut serialized = table.raw_bytes().to_owned(); // Shift the data so we mess up alignment. We want to test the table under // realistic conditions where we cannot expect any specific alignment. serialized.insert(0, 0xFFu8); let table = HashTable::::from_raw_bytes(&serialized[1..]).unwrap(); let get = |key: &TestKey| table.get(key); b.iter(|| { for _ in 0..LOOKUP_ITERATIONS { for (index, &(key, value)) in test_data.iter().enumerate() { if index_contained(index) { assert!(get_value(&get, &key) == Some(value)); } else { assert!(get_value(&get, &key).is_none()); } } } }) } fn bench_odht_fx_insert(b: &mut test::Bencher, num_values: usize, load_factor_percent: u8) { let test_data = crate::generate_test_data(num_values); b.iter(|| { for _ in 0..INSERT_ITERATIONS { let mut table = HashTableOwned::::with_capacity(10, load_factor_percent); let mut insert = |key: TestKey, value: u32| table.insert(&key, &value); for (key, value) in test_data.iter() { assert!(insert_value(&mut insert, *key, *value).is_none()); } } }) } fn bench_std_fx_lookup(b: &mut test::Bencher, num_values: usize) { let test_data = crate::generate_test_data(num_values); let table = crate::generate_std_hash_table(&test_data); let get = |key: &TestKey| table.get(key).cloned(); b.iter(|| { for _ in 0..LOOKUP_ITERATIONS { for (index, &(key, value)) in test_data.iter().enumerate() { if index_contained(index) { assert!(get_value(&get, &key) == Some(value)); } else { assert!(get_value(&get, &key).is_none()); } } } }) } fn bench_std_fx_insert(b: &mut test::Bencher, num_values: usize) { let test_data = crate::generate_test_data(num_values); b.iter(|| { for _ in 0..INSERT_ITERATIONS { let mut table = FxHashMap::default(); let mut insert = |key: TestKey, value: u32| -> Option { table.insert(key, value) }; for (key, value) in test_data.iter() { assert!(insert_value(&mut insert, *key, *value).is_none()); } } }) } macro_rules! bench { ($name:ident, $num_values:expr) => { mod $name { #[bench] fn lookup_odht_fx_load_87(b: &mut test::Bencher) { crate::bench_odht_fx_lookup(b, $num_values, 87); } #[bench] fn insert_odht_fx_load_87(b: &mut test::Bencher) { crate::bench_odht_fx_insert(b, $num_values, 87); } #[bench] fn lookup_std_fx(b: &mut test::Bencher) { crate::bench_std_fx_lookup(b, $num_values); } #[bench] fn insert_std_fx(b: &mut test::Bencher) { crate::bench_std_fx_insert(b, $num_values); } } }; } // These numbers are chosen so that we get an actual load factor of ~87% // taking into account that slot counts are always rounded up to the next // power of two. bench!(____n13, 13); bench!(____n55, 55); bench!(___n444, 444); bench!(__n3550, 3550); bench!(_n57000, 57000);