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: 4 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "fchashmap"
version = "0.1.3"
version = "0.2.0"
authors = ["Simsys <winfried.simon@gmail.com>"]
edition = "2018"
edition = "2024"
license = "MIT OR Apache-2.0"
description = "A fixed capacity no_std hashmap"
repository = "https://github.com/Simsys/fchashmap"
Expand All @@ -11,15 +11,10 @@ keywords = ["no-std", "static", "no-heap", "embedded"]
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"

[dependencies.arrayvec]
version = "0.7.0"
default-features = false
arrayvec = { version = "0.7.8", default-features = false }
hash32 = "1.0.0"

[dev-dependencies]
hash32-derive = "0.1.0"
rand_xorshift = "0.3.0"
rand_core = "0.6.2"
14 changes: 6 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
mod map;
use map::{Iter, IterMut, Map};
//use std::{fmt::Display};
use core::hash::Hash;
use core::{borrow::Borrow, fmt, iter::FromIterator, ops};
use hash32::Hash;

/// A fixed capacity no_std hashmap.
///
Expand All @@ -34,16 +34,15 @@ use hash32::Hash;
///
/// ```
/// use fchashmap::FcHashMap;
/// use hash32_derive::Hash32;
/// use hash32::Hash;
/// use core::hash::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 +72,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
17 changes: 10 additions & 7 deletions src/map.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#![allow(dead_code)]
use arrayvec::ArrayVec;
use core::hash::{BuildHasher, BuildHasherDefault, Hash};
use core::{borrow::Borrow, mem, slice};
use hash32::{BuildHasher, BuildHasherDefault, FnvHasher, Hash, Hasher};
use hash32::{Hasher, Murmur3Hasher};

#[derive(Clone, Copy, PartialEq)]
struct HashValue(u16);
Expand Down Expand Up @@ -37,7 +38,10 @@ struct HashIndex {
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 }
Self {
hash,
b_idx: b_idx as u16,
}
}

// Clear actual hash index an mark it as empty
Expand All @@ -61,11 +65,10 @@ pub struct Bucket<K, V> {
pub struct Map<K, V, const CAP: usize> {
pub buckets: ArrayVec<Bucket<K, V>, CAP>,
hash_table: [HashIndex; CAP],
build_hasher: BuildHasherDefault<FnvHasher>,
build_hasher: BuildHasherDefault<Murmur3Hasher>,
}

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 +101,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 +145,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
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