diff --git a/Cargo.toml b/Cargo.toml index 08ef4ca..77f3c7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,18 @@ categories = ["no-std", "embedded", "data-structures"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -hash32 = "0.2.1" +hash32 = "0.3.0" +cfg-if = "1.0.0" [dependencies.arrayvec] -version = "0.7.0" +version = "0.7.2" default-features = false [dev-dependencies] -hash32-derive = "0.1.0" +hash32-derive = "0.1.1" rand_xorshift = "0.3.0" -rand_core = "0.6.2" +rand_core = "0.6.3" + +[features] +default = [] +hugesize = [] diff --git a/src/lib.rs b/src/lib.rs index 1cbf05e..70b876d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,11 +11,10 @@ //! [2](https://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/)) //! the functionality is explained very nicely. #![cfg_attr(not(test), no_std)] -mod map; +pub mod map; use map::{Iter, IterMut, Map}; //use std::{fmt::Display}; -use core::{borrow::Borrow, fmt, iter::FromIterator, ops}; -use hash32::Hash; +use core::{borrow::Borrow, fmt, hash::Hash, iter::FromIterator, ops}; /// A fixed capacity no_std hashmap. /// @@ -34,8 +33,6 @@ use hash32::Hash; /// /// ``` /// use fchashmap::FcHashMap; -/// use hash32_derive::Hash32; -/// use hash32::Hash; /// /// #[derive(Debug)] /// struct Reading { @@ -43,7 +40,7 @@ use hash32::Hash; /// humidy: f32, /// } /// -/// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash32)] +/// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] /// struct DeviceId([u8; 8]); /// /// impl DeviceId { @@ -73,11 +70,11 @@ use hash32::Hash; /// /// assert!(fc_hash_map.get(&dev3).is_none()); /// ``` -/// +/// /// ## Performance /// /// The following diagram shows the timing behavior on a Cortex M4f system (STM32F3) at 72 MHz. It -/// can be seen that the performance of the hashmap decreases significantly from a fill margin of +/// can be seen that the performance of the hashmap decreases significantly from a fill margin of /// about 80%. /// /// ![Image](https://raw.githubusercontent.com/Simsys/fchashmap/master/benches/cm4_performance/fchashmap.png) @@ -85,8 +82,7 @@ pub struct FcHashMap { map: Map, } -impl FcHashMap -{ +impl FcHashMap { // pub fn show(&self) { self.map.show() } /// Creates an empty HashMap. diff --git a/src/map.rs b/src/map.rs index 93399a5..a1f514e 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,53 +1,117 @@ #![allow(dead_code)] use arrayvec::ArrayVec; -use core::{borrow::Borrow, mem, slice}; -use hash32::{BuildHasher, BuildHasherDefault, FnvHasher, Hash, Hasher}; +use core::{ + borrow::Borrow, + hash::{BuildHasher, Hash}, + mem, slice, +}; +use hash32::{BuildHasherDefault, FnvHasher, Hasher}; + +cfg_if::cfg_if! { + if #[cfg(feature = "hugesize")] { + use core::convert::TryFrom; + + #[derive(Clone, Copy, PartialEq)] + struct HashValue(u32); + + // There are up to 0x7fffffff (2^31 - 1) elements allowed. The first bit of u32 is used to mark + // a empty element + const HASH_VALUE_IS_EMPTY: HashValue = HashValue(0x80000000); + + impl HashValue { + // Drop the negative sign + fn new(hash: u32) -> Self { + HashValue((hash & 0x7fffffff) as u32) + } -#[derive(Clone, Copy, PartialEq)] -struct HashValue(u16); + // Calulate expected index from hash value + fn desired_h_idx(&self, mask: usize) -> usize { + usize::try_from(self.0).unwrap() & mask + } -// There are up to 0x7fff (32767) elements allowed. The first bit of u16 is used to mark -// a empty element -const HASH_VALUE_IS_EMPTY: HashValue = HashValue(0x8000); + // Calculate distance from expected index from current index + fn h_idx_distance(&self, mask: usize, current_h_idx: usize) -> usize { + current_h_idx.wrapping_sub(self.desired_h_idx(mask) as usize) & mask + } + } -impl HashValue { - // Create 15 bit hash value from u32 hash - fn new(hash: u32) -> Self { - HashValue((hash & 0x7fff) as u16) - } + // A Combination of hash value and index into the bucket list + #[derive(Clone, Copy)] + struct HashIndex { + hash: HashValue, + b_idx: u32, + } - // Calulate expected index from hash value - fn desired_h_idx(&self, mask: usize) -> usize { - usize::from(self.0) & mask - } + impl HashIndex { + // Create a nuew hash index from given parameters + fn new(hash: HashValue, b_idx: usize) -> Self { + Self { + hash, + b_idx: b_idx as u32, + } + } - // Calculate distance from expected index from current index - fn h_idx_distance(&self, mask: usize, current_h_idx: usize) -> usize { - current_h_idx.wrapping_sub(self.desired_h_idx(mask) as usize) & mask - } -} + // Clear actual hash index an mark it as empty + fn clear(&mut self) { + self.hash = HASH_VALUE_IS_EMPTY; + } -// A Combination of hash value and index into the bucket list -#[derive(Clone, Copy)] -struct HashIndex { - hash: HashValue, - b_idx: u16, -} + // Check if hash index is empty + fn is_empty(&self) -> bool { + self.hash == HASH_VALUE_IS_EMPTY + } + } + } else { + #[derive(Clone, Copy, PartialEq)] + struct HashValue(u16); + + // There are up to 0x7fff (32767) elements allowed. The first bit of u16 is used to mark + // a empty element + const HASH_VALUE_IS_EMPTY: HashValue = HashValue(0x8000); + + impl HashValue { + // Create 15 bit hash value from u32 hash + fn new(hash: u32) -> Self { + HashValue((hash & 0x7fff) as u16) + } -impl HashIndex { - // Create a nuew hash index from given parameters - fn new(hash: HashValue, b_idx: usize) -> Self { - Self { hash, b_idx: b_idx as u16 } - } + // Calulate expected index from hash value + fn desired_h_idx(&self, mask: usize) -> usize { + usize::from(self.0) & mask + } - // Clear actual hash index an mark it as empty - fn clear(&mut self) { - self.hash = HASH_VALUE_IS_EMPTY; - } + // Calculate distance from expected index from current index + fn h_idx_distance(&self, mask: usize, current_h_idx: usize) -> usize { + current_h_idx.wrapping_sub(self.desired_h_idx(mask) as usize) & mask + } + } + + // A Combination of hash value and index into the bucket list + #[derive(Clone, Copy)] + struct HashIndex { + hash: HashValue, + b_idx: u16, + } - // Check if hash index is empty - fn is_empty(&self) -> bool { - self.hash == HASH_VALUE_IS_EMPTY + impl HashIndex { + // Create a nuew hash index from given parameters + fn new(hash: HashValue, b_idx: usize) -> Self { + Self { + hash, + b_idx: b_idx as u16, + } + } + + // Clear actual hash index an mark it as empty + fn clear(&mut self) { + self.hash = HASH_VALUE_IS_EMPTY; + } + + // Check if hash index is empty + fn is_empty(&self) -> bool { + self.hash == HASH_VALUE_IS_EMPTY + } + } } } @@ -64,8 +128,7 @@ pub struct Map { build_hasher: BuildHasherDefault, } -impl Map -{ +impl Map { // Create a new map pub fn new() -> Self { debug_assert!((Self::capacity() as u32) < u32::MAX); @@ -98,7 +161,7 @@ impl Map { let mut h = self.build_hasher.build_hasher(); key.hash(&mut h); - HashValue::new(h.finish()) + HashValue::new(h.finish32()) } // Inserts a key-value pair into the map. @@ -142,7 +205,7 @@ impl Map if next_hash_index.is_empty() { // We found the right place: store and return *next_hash_index = hash_index; - unsafe { self.buckets.push_unchecked( Bucket { key, value, hash }) } + unsafe { self.buckets.push_unchecked(Bucket { key, value, hash }) } return Ok(None); } else { // Replace HashIndexs and continue shifting and searching for a vacancy @@ -211,7 +274,7 @@ impl Map // The HashIndex at location h_idx and the bucket at location b_idx are deleted. self.hash_table[found_h_idx].clear(); let deleted_bucket = self.buckets.swap_pop(found_b_idx).unwrap(); // ArrayVec; - //let deleted_bucket = unsafe { self.buckets.swap_remove_unchecked(found_b_idx) }; // heapless::Vec; + //let deleted_bucket = unsafe { self.buckets.swap_remove_unchecked(found_b_idx) }; // heapless::Vec; // Correct index that points to the entry that had to swap places. // This has only to be done, if wass not the last element in self.buckets diff --git a/tests/monte_carlo.rs b/tests/monte_carlo.rs index e65ab69..ab68510 100644 --- a/tests/monte_carlo.rs +++ b/tests/monte_carlo.rs @@ -1,18 +1,16 @@ use fchashmap::FcHashMap; -use rand_xorshift::XorShiftRng; use rand_core::{RngCore, SeedableRng}; +use rand_xorshift::XorShiftRng; use std::collections::HashMap; - struct MonteCarlo { - fc_hashmap: FcHashMap::, - std_hashmap: HashMap::, + fc_hashmap: FcHashMap, + std_hashmap: HashMap, } const MAP_SIZE: usize = 16384; const SEED: u64 = 1234567890987654321; - impl MonteCarlo { fn new() -> Self { Self { @@ -30,14 +28,28 @@ impl MonteCarlo { match r_fc { Ok(r_v) => { if r_v != r_std { - println!("Error 1, len {}, key {}, value {}, r_v{:?}, r_std {:?}", self.fc_hashmap.len(), key, value, r_v, r_std); + println!( + "Error 1, len {}, key {}, value {}, r_v{:?}, r_std {:?}", + self.fc_hashmap.len(), + key, + value, + r_v, + r_std + ); }; assert_eq!(r_v, r_std); - }, + } Err(e) => { - println!("Error 2, len {}, key {}, value {}, e{:?}, r_std {:?}", self.fc_hashmap.len(), key, value, e, r_std); + println!( + "Error 2, len {}, key {}, value {}, e{:?}, r_std {:?}", + self.fc_hashmap.len(), + key, + value, + e, + r_std + ); assert!(false); - }, + } } } @@ -48,7 +60,13 @@ impl MonteCarlo { let r_std = self.std_hashmap.remove(&key); if r_fc != r_std { - println!("Error 3, len {}, key {}, r_fc{:?}, r_std {:?}", self.fc_hashmap.len(), key, r_fc, r_std); + println!( + "Error 3, len {}, key {}, r_fc{:?}, r_std {:?}", + self.fc_hashmap.len(), + key, + r_fc, + r_std + ); }; assert_eq!(r_fc, r_std); } @@ -60,7 +78,13 @@ impl MonteCarlo { let r_std = self.std_hashmap.get(&key); if r_fc != r_std { - println!("Error 4, len {}, key {}, r_fc{:?}, r_std {:?}", self.fc_hashmap.len(), key, r_fc, r_std); + println!( + "Error 4, len {}, key {}, r_fc{:?}, r_std {:?}", + self.fc_hashmap.len(), + key, + r_fc, + r_std + ); }; assert_eq!(r_fc, r_std); } @@ -103,7 +127,7 @@ impl MonteCarlo { // First, we fill the map at 50% let mut rng = XorShiftRng::seed_from_u64(SEED); loop { - if self.fc_hashmap.len() >= MAP_SIZE/2 { + if self.fc_hashmap.len() >= MAP_SIZE / 2 { break; } self.insert(&mut rng); @@ -117,7 +141,13 @@ impl MonteCarlo { let r_std = self.std_hashmap.get(&key); if r_fc != r_std { - println!("Error 6, len {}, key {}, r_fc{:?}, r_std {:?}", self.fc_hashmap.len(), key, r_fc, r_std); + println!( + "Error 6, len {}, key {}, r_fc{:?}, r_std {:?}", + self.fc_hashmap.len(), + key, + r_fc, + r_std + ); }; assert_eq!(r_fc, r_std); } @@ -125,7 +155,7 @@ impl MonteCarlo { } #[test] -fn monte_carlo () { +fn monte_carlo() { let mut m = MonteCarlo::new(); m.test_1(); m.test_2();