HashMap
with_hasher
In Rust we can specify a hash function for the HashMap
(and HashSet
) structs. By doing this, we can improve performance for programs that use hashtable lookups.
To specify a custom Hasher
, we use HashMap::with_hasher
(or with_hasher_and_capacity
). The HashMap
has a different type (the Hasher
is the third generic type).
This program uses 2 different Hasher
arguments to create HashMaps
. It accesses the Hasher
from the standard library, and then uses an external crate (ahash).
HashMap::with_hasher
function. We use the Hasher
from the standard library—this is the same as calling new()
.use std::collections::*; fn main() { // Part 1: use HashMap with default hasher from standard library. let mut h = HashMap::with_hasher(std::hash::RandomState::default()); h.insert("bird", 10); if let Some(result) = h.get("bird") { println!("{result}"); } // Part 2: use HashMap with ahash Hasher (add ahash dependency first). let mut h = HashMap::with_hasher(ahash::RandomState::default()); h.insert("bird", 10); if let Some(result) = h.get("bird") { println!("{result}"); } }10 10
When we create a HashMap
with a custom Hasher
(using with_hasher
), the type of the HashMap
is affected. The custom Hasher
type is the third generic type of a HashMap
.
HashMap
types.use std::collections::*; // Part 1: the type of a HashMap with a custom hasher has 3 generic arguments. struct Example { h: HashMap<String, usize, ahash::RandomState>, } fn main() { // Part 2: create the HashMap. let mut h = HashMap::with_hasher(ahash::RandomState::default()); h.insert("cat".to_string(), 100usize); // Part 3: store the HashMap within another struct. let example = Example { h }; println!("{}", example.h.len()); }1
It it worth the effort to replace the default hash function in Rust programs? This benchmark aims to determine if the "ahash" crate is worth using.
HashMap
with the default hasher—this could just be a call to new()
.Hasher
, which is also called RandomState
. We perform the same lookups in both versions of the code.Hasher
is many times faster than the default Hasher
in Rust as of 2024.use std::collections::*; use std::time::*; fn main() { let mut map = HashMap::with_hasher(std::hash::RandomState::default()); map.insert("bird", 0); map.insert("frog", 0); map.insert("dog", 0); let mut map2 = HashMap::with_hasher(ahash::RandomState::default()); map2.insert("bird", 0); map2.insert("frog", 0); map2.insert("dog", 0); if let Ok(max) = "100000000".parse::<usize>() { let mut count = 0; // Version 1: use HashMap with standard library hasher. let t0 = Instant::now(); for _ in 0..max { if let Some(_) = map.get("frog") { count += 1; } } println!("{} ms", t0.elapsed().as_millis()); // Version 2: use HashMap with ahash hasher (from crate). let t1 = Instant::now(); for _ in 0..max { if let Some(_) = map2.get("frog") { count += 1; } } println!("{} ms", t1.elapsed().as_millis()); println!("{}", count); } }779 ms 102 ms 200000000
HashMaps
and HashSets
in Rust are well-optimized, but with a custom Hasher
from a crate such as Ahash we can achieve even higher performance. For some programs this is a big speedup.