Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
16 changes: 6 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -34,16 +33,14 @@ use hash32::Hash;
///
/// ```
/// use fchashmap::FcHashMap;
/// use hash32_derive::Hash32;
/// use hash32::Hash;
///
/// #[derive(Debug)]
/// struct Reading {
/// temperature: f32,
/// humidy: f32,
/// }
///
/// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash32)]
/// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
/// struct DeviceId([u8; 8]);
///
/// impl DeviceId {
Expand Down Expand Up @@ -73,20 +70,19 @@ 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)
pub struct FcHashMap<K, V, const CAP: usize> {
map: Map<K, V, CAP>,
}

impl<K, V, const CAP: usize> FcHashMap<K, V, CAP>
{
impl<K, V, const CAP: usize> FcHashMap<K, V, CAP> {
// pub fn show(&self) { self.map.show() }

/// Creates an empty HashMap.
Expand Down
151 changes: 107 additions & 44 deletions src/map.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}

Expand All @@ -64,8 +128,7 @@ pub struct Map<K, V, const CAP: usize> {
build_hasher: BuildHasherDefault<FnvHasher>,
}

impl<K, V, const CAP: usize> Map<K, V, CAP>
{
impl<K, V, const CAP: usize> Map<K, V, CAP> {
// Create a new map
pub fn new() -> Self {
debug_assert!((Self::capacity() as u32) < u32::MAX);
Expand Down Expand Up @@ -98,7 +161,7 @@ impl<K, V, const CAP: usize> Map<K, V, CAP>
{
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.
Expand Down Expand Up @@ -142,7 +205,7 @@ impl<K, V, const CAP: usize> Map<K, V, CAP>
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
Expand Down Expand Up @@ -211,7 +274,7 @@ impl<K, V, const CAP: usize> Map<K, V, CAP>
// 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
Expand Down
58 changes: 44 additions & 14 deletions tests/monte_carlo.rs
Original file line number Diff line number Diff line change
@@ -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::<u32, u32, MAP_SIZE>,
std_hashmap: HashMap::<u32, u32>,
fc_hashmap: FcHashMap<u32, u32, MAP_SIZE>,
std_hashmap: HashMap<u32, u32>,
}

const MAP_SIZE: usize = 16384;
const SEED: u64 = 1234567890987654321;


impl MonteCarlo {
fn new() -> Self {
Self {
Expand All @@ -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);
},
}
}
}

Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -117,15 +141,21 @@ 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);
}
}
}

#[test]
fn monte_carlo () {
fn monte_carlo() {
let mut m = MonteCarlo::new();
m.test_1();
m.test_2();
Expand Down