kovan_map/
hashmap.rs

1//! High-Performance Lock-Free Concurrent Hash Map (FoldHash + Large Table).
2//!
3//! # Strategy for Beating Dashmap
4//!
5//! 1. **FoldHash**: We use `foldhash::fast::FixedState` for blazing fast, quality hashing.
6//!    This replaces the custom FxHash implementation.
7//! 2. **Oversized Bucket Array**: We bump buckets to 524,288.
8//!    - Memory footprint: ~4MB (Fits in L3 cache).
9//!    - Load Factor at 100k items: ~0.19.
10//!    - Result: almost ZERO collisions. Lookups become a single pointer dereference.
11//! 3. **Optimized Node Layout**: Fields are ordered `hash -> key -> value -> next` to
12//!    optimize CPU cache line usage during checks.
13//!
14//! # Architecture
15//! - **Buckets**: Array of Atomic pointers (null-initialized).
16//! - **Nodes**: Singly linked list.
17//! - **Concurrency**: CAS-based lock-free insertion/removal.
18
19extern crate alloc;
20
21#[cfg(feature = "std")]
22extern crate std;
23
24use alloc::boxed::Box;
25use alloc::vec::Vec;
26use core::borrow::Borrow;
27use core::hash::{BuildHasher, Hash};
28use core::sync::atomic::Ordering;
29use foldhash::fast::FixedState;
30use kovan::{Atomic, Shared, pin, retire};
31
32/// Number of buckets.
33/// 524,288 = 2^19.
34/// Size of bucket array = 512k * 8 bytes = 4MB.
35/// This is large enough to minimize collisions for 100k-500k items
36/// while still fitting in modern L3 caches.
37const BUCKET_COUNT: usize = 524_288;
38
39/// A simple exponential backoff for reducing contention.
40struct Backoff {
41    step: u32,
42}
43
44impl Backoff {
45    #[inline(always)]
46    fn new() -> Self {
47        Self { step: 0 }
48    }
49
50    #[inline(always)]
51    fn spin(&mut self) {
52        for _ in 0..(1 << self.step.min(6)) {
53            core::hint::spin_loop();
54        }
55        if self.step <= 6 {
56            self.step += 1;
57        }
58    }
59}
60
61/// Node in the lock-free linked list.
62/// Layout optimized for scanning: `hash` and `key` are checked first.
63struct Node<K, V> {
64    hash: u64,
65    key: K,
66    value: V,
67    next: Atomic<Node<K, V>>,
68}
69
70/// High-Performance Lock-Free Map.
71pub struct HashMap<K: 'static, V: 'static, S = FixedState> {
72    buckets: Box<[Atomic<Node<K, V>>]>,
73    mask: usize,
74    hasher: S,
75}
76
77#[cfg(feature = "std")]
78impl<K, V> HashMap<K, V, FixedState>
79where
80    K: Hash + Eq + Clone + 'static,
81    V: Clone + 'static,
82{
83    /// Creates a new empty hash map with FoldHash (FixedState).
84    pub fn new() -> Self {
85        Self::with_hasher(FixedState::default())
86    }
87}
88
89impl<K, V, S> HashMap<K, V, S>
90where
91    K: Hash + Eq + Clone + 'static,
92    V: Clone + 'static,
93    S: BuildHasher,
94{
95    /// Creates a new hash map with custom hasher.
96    pub fn with_hasher(hasher: S) -> Self {
97        let mut buckets = Vec::with_capacity(BUCKET_COUNT);
98        for _ in 0..BUCKET_COUNT {
99            buckets.push(Atomic::null());
100        }
101
102        Self {
103            buckets: buckets.into_boxed_slice(),
104            mask: BUCKET_COUNT - 1,
105            hasher,
106        }
107    }
108
109    #[inline(always)]
110    fn get_bucket_idx(&self, hash: u64) -> usize {
111        (hash as usize) & self.mask
112    }
113
114    #[inline(always)]
115    fn get_bucket(&self, idx: usize) -> &Atomic<Node<K, V>> {
116        unsafe { self.buckets.get_unchecked(idx) }
117    }
118
119    /// Optimized get operation.
120    pub fn get<Q>(&self, key: &Q) -> Option<V>
121    where
122        K: Borrow<Q>,
123        Q: Hash + Eq + ?Sized,
124    {
125        let hash = self.hasher.hash_one(key);
126        let idx = self.get_bucket_idx(hash);
127        let bucket = self.get_bucket(idx);
128
129        let guard = pin();
130        let mut current = bucket.load(Ordering::Acquire, &guard);
131
132        while !current.is_null() {
133            unsafe {
134                let node = current.deref();
135                // Check hash first (integer compare is fast)
136                if node.hash == hash && node.key.borrow() == key {
137                    return Some(node.value.clone());
138                }
139                current = node.next.load(Ordering::Acquire, &guard);
140            }
141        }
142        None
143    }
144
145    /// Checks if the key exists.
146    pub fn contains_key<Q>(&self, key: &Q) -> bool
147    where
148        K: Borrow<Q>,
149        Q: Hash + Eq + ?Sized,
150    {
151        self.get(key).is_some()
152    }
153
154    /// Insert a key-value pair.
155    pub fn insert(&self, key: K, value: V) -> Option<V> {
156        let hash = self.hasher.hash_one(&key);
157        let idx = self.get_bucket_idx(hash);
158        let bucket = self.get_bucket(idx);
159        let mut backoff = Backoff::new();
160
161        let guard = pin();
162
163        'outer: loop {
164            // 1. Search for existing key to update
165            let mut prev_link = bucket;
166            let mut current = prev_link.load(Ordering::Acquire, &guard);
167
168            while !current.is_null() {
169                unsafe {
170                    let node = current.deref();
171
172                    if node.hash == hash && node.key == key {
173                        // Key matches. Replace node (COW style).
174                        let next = node.next.load(Ordering::Relaxed, &guard);
175                        let old_value = node.value.clone();
176
177                        // Create new node pointing to existing next
178                        let new_node = Box::into_raw(Box::new(Node {
179                            hash,
180                            key: key.clone(),
181                            value: value.clone(),
182                            next: Atomic::new(next.as_raw()),
183                        }));
184
185                        match prev_link.compare_exchange(
186                            current,
187                            Shared::from_raw(new_node),
188                            Ordering::Release,
189                            Ordering::Relaxed,
190                            &guard,
191                        ) {
192                            Ok(_) => {
193                                retire(current.as_raw());
194                                return Some(old_value);
195                            }
196                            Err(_) => {
197                                // CAS failed.
198                                drop(Box::from_raw(new_node));
199                                backoff.spin();
200                                continue 'outer;
201                            }
202                        }
203                    }
204
205                    prev_link = &node.next;
206                    current = node.next.load(Ordering::Acquire, &guard);
207                }
208            }
209
210            // 2. Key not found. Insert at TAIL (prev_link).
211            // Note: In low collision scenarios, prev_link is often the bucket head itself.
212            let new_node_ptr = Box::into_raw(Box::new(Node {
213                hash,
214                key: key.clone(),
215                value: value.clone(),
216                next: Atomic::null(),
217            }));
218
219            // Try to swap NULL -> NEW_NODE
220            match prev_link.compare_exchange(
221                unsafe { Shared::from_raw(core::ptr::null_mut()) },
222                unsafe { Shared::from_raw(new_node_ptr) },
223                Ordering::Release,
224                Ordering::Relaxed,
225                &guard,
226            ) {
227                Ok(_) => return None,
228                Err(actual_val) => {
229                    // Contention at the tail.
230                    // Someone appended something while we were watching.
231                    // Check if they inserted OUR key.
232                    unsafe {
233                        let actual_node = actual_val.deref();
234                        if actual_node.hash == hash && actual_node.key == key {
235                            // Race lost, but key exists now. We should retry to update it.
236                            drop(Box::from_raw(new_node_ptr));
237                            backoff.spin();
238                            continue 'outer;
239                        }
240                    }
241
242                    // Otherwise, just retry the search/append loop
243                    unsafe {
244                        drop(Box::from_raw(new_node_ptr));
245                    }
246                    backoff.spin();
247                    continue 'outer;
248                }
249            }
250        }
251    }
252
253    /// Insert a key-value pair only if the key does not exist.
254    /// Returns `None` if inserted, `Some(existing_value)` if the key already exists.
255    pub fn insert_if_absent(&self, key: K, value: V) -> Option<V> {
256        let hash = self.hasher.hash_one(&key);
257        let idx = self.get_bucket_idx(hash);
258        let bucket = self.get_bucket(idx);
259        let mut backoff = Backoff::new();
260
261        let guard = pin();
262
263        'outer: loop {
264            // 1. Search for existing key
265            let mut prev_link = bucket;
266            let mut current = prev_link.load(Ordering::Acquire, &guard);
267
268            while !current.is_null() {
269                unsafe {
270                    let node = current.deref();
271
272                    if node.hash == hash && node.key == key {
273                        // Key matches. Return existing value.
274                        return Some(node.value.clone());
275                    }
276
277                    prev_link = &node.next;
278                    current = node.next.load(Ordering::Acquire, &guard);
279                }
280            }
281
282            // 2. Key not found. Insert at TAIL (prev_link).
283            let new_node_ptr = Box::into_raw(Box::new(Node {
284                hash,
285                key: key.clone(),
286                value: value.clone(),
287                next: Atomic::null(),
288            }));
289
290            // Try to swap NULL -> NEW_NODE
291            match prev_link.compare_exchange(
292                unsafe { Shared::from_raw(core::ptr::null_mut()) },
293                unsafe { Shared::from_raw(new_node_ptr) },
294                Ordering::Release,
295                Ordering::Relaxed,
296                &guard,
297            ) {
298                Ok(_) => return None,
299                Err(actual_val) => {
300                    // Contention at the tail.
301                    unsafe {
302                        let actual_node = actual_val.deref();
303                        if actual_node.hash == hash && actual_node.key == key {
304                            // Race lost, key exists now. Return existing value.
305                            drop(Box::from_raw(new_node_ptr));
306                            return Some(actual_node.value.clone());
307                        }
308                    }
309
310                    // Otherwise, just retry the search/append loop
311                    unsafe {
312                        drop(Box::from_raw(new_node_ptr));
313                    }
314                    backoff.spin();
315                    continue 'outer;
316                }
317            }
318        }
319    }
320
321    /// Remove a key-value pair.
322    pub fn remove<Q>(&self, key: &Q) -> Option<V>
323    where
324        K: Borrow<Q>,
325        Q: Hash + Eq + ?Sized,
326    {
327        let hash = self.hasher.hash_one(key);
328        let idx = self.get_bucket_idx(hash);
329        let bucket = self.get_bucket(idx);
330        let mut backoff = Backoff::new();
331
332        let guard = pin();
333
334        loop {
335            let mut prev_link = bucket;
336            let mut current = prev_link.load(Ordering::Acquire, &guard);
337
338            while !current.is_null() {
339                unsafe {
340                    let node = current.deref();
341
342                    if node.hash == hash && node.key.borrow() == key {
343                        let next = node.next.load(Ordering::Acquire, &guard);
344                        let old_value = node.value.clone();
345
346                        match prev_link.compare_exchange(
347                            current,
348                            next,
349                            Ordering::Release,
350                            Ordering::Relaxed,
351                            &guard,
352                        ) {
353                            Ok(_) => {
354                                retire(current.as_raw());
355                                return Some(old_value);
356                            }
357                            Err(_) => {
358                                backoff.spin();
359                                break; // Break inner loop to retry outer
360                            }
361                        }
362                    }
363
364                    prev_link = &node.next;
365                    current = node.next.load(Ordering::Acquire, &guard);
366                }
367            }
368
369            if current.is_null() {
370                return None;
371            }
372        }
373    }
374
375    /// Clear the map.
376    pub fn clear(&self) {
377        let guard = pin();
378
379        for bucket in self.buckets.iter() {
380            loop {
381                let head = bucket.load(Ordering::Acquire, &guard);
382                if head.is_null() {
383                    break;
384                }
385
386                // Try to unlink the whole chain at once
387                match bucket.compare_exchange(
388                    head,
389                    unsafe { Shared::from_raw(core::ptr::null_mut()) },
390                    Ordering::Release,
391                    Ordering::Relaxed,
392                    &guard,
393                ) {
394                    Ok(_) => {
395                        // Retire all nodes in the chain
396                        unsafe {
397                            let mut current = head;
398                            while !current.is_null() {
399                                let node = current.deref();
400                                let next = node.next.load(Ordering::Relaxed, &guard);
401                                retire(current.as_raw());
402                                current = next;
403                            }
404                        }
405                        break;
406                    }
407                    Err(_) => {
408                        // Contention, retry
409                        continue;
410                    }
411                }
412            }
413        }
414    }
415
416    /// Returns true if the map is empty.
417    pub fn is_empty(&self) -> bool {
418        self.len() == 0
419    }
420
421    /// Returns the number of elements in the map.
422    /// Note: This is an O(N) operation as it scans all buckets.
423    pub fn len(&self) -> usize {
424        let mut count = 0;
425        let guard = pin();
426        for bucket in self.buckets.iter() {
427            let mut current = bucket.load(Ordering::Acquire, &guard);
428            while !current.is_null() {
429                unsafe {
430                    let node = current.deref();
431                    count += 1;
432                    current = node.next.load(Ordering::Acquire, &guard);
433                }
434            }
435        }
436        count
437    }
438
439    /// Returns an iterator over the map entries.
440    /// Yields (K, V) clones.
441    pub fn iter(&self) -> Iter<'_, K, V, S> {
442        Iter {
443            map: self,
444            bucket_idx: 0,
445            current: core::ptr::null(),
446            guard: pin(),
447        }
448    }
449
450    /// Returns an iterator over the map keys.
451    /// Yields K clones.
452    pub fn keys(&self) -> Keys<'_, K, V, S> {
453        Keys { iter: self.iter() }
454    }
455
456    /// Get the underlying hasher itself.
457    pub fn hasher(&self) -> &S {
458        &self.hasher
459    }
460}
461
462/// Iterator over HashMap entries.
463pub struct Iter<'a, K: 'static, V: 'static, S> {
464    map: &'a HashMap<K, V, S>,
465    bucket_idx: usize,
466    current: *const Node<K, V>,
467    guard: kovan::Guard,
468}
469
470impl<'a, K, V, S> Iterator for Iter<'a, K, V, S>
471where
472    K: Clone,
473    V: Clone,
474{
475    type Item = (K, V);
476
477    fn next(&mut self) -> Option<Self::Item> {
478        loop {
479            if !self.current.is_null() {
480                unsafe {
481                    let node = &*self.current;
482                    // Advance current
483                    self.current = node.next.load(Ordering::Acquire, &self.guard).as_raw();
484                    return Some((node.key.clone(), node.value.clone()));
485                }
486            }
487
488            // Move to next bucket
489            if self.bucket_idx >= self.map.buckets.len() {
490                return None;
491            }
492
493            let bucket = unsafe { self.map.buckets.get_unchecked(self.bucket_idx) };
494            self.bucket_idx += 1;
495            self.current = bucket.load(Ordering::Acquire, &self.guard).as_raw();
496        }
497    }
498}
499
500/// Iterator over HashMap keys.
501pub struct Keys<'a, K: 'static, V: 'static, S> {
502    iter: Iter<'a, K, V, S>,
503}
504
505impl<'a, K, V, S> Iterator for Keys<'a, K, V, S>
506where
507    K: Clone,
508    V: Clone,
509{
510    type Item = K;
511
512    fn next(&mut self) -> Option<Self::Item> {
513        self.iter.next().map(|(k, _)| k)
514    }
515}
516
517impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S>
518where
519    K: Hash + Eq + Clone + 'static,
520    V: Clone + 'static,
521    S: BuildHasher,
522{
523    type Item = (K, V);
524    type IntoIter = Iter<'a, K, V, S>;
525
526    fn into_iter(self) -> Self::IntoIter {
527        self.iter()
528    }
529}
530
531#[cfg(feature = "std")]
532impl<K, V> Default for HashMap<K, V, FixedState>
533where
534    K: Hash + Eq + Clone + 'static,
535    V: Clone + 'static,
536{
537    fn default() -> Self {
538        Self::new()
539    }
540}
541
542// SAFETY: HashMap is Send/Sync if K, V are.
543unsafe impl<K: Send, V: Send, S: Send> Send for HashMap<K, V, S> {}
544unsafe impl<K: Sync, V: Sync, S: Sync> Sync for HashMap<K, V, S> {}
545
546impl<K, V, S> Drop for HashMap<K, V, S> {
547    fn drop(&mut self) {
548        let guard = pin();
549
550        for bucket in self.buckets.iter() {
551            let mut current = bucket.load(Ordering::Acquire, &guard);
552
553            unsafe {
554                while !current.is_null() {
555                    let node = current.deref();
556                    let next = node.next.load(Ordering::Relaxed, &guard);
557                    // Drop the Box manually
558                    retire(current.as_raw());
559                    current = next;
560                }
561            }
562        }
563    }
564}
565
566#[cfg(test)]
567mod tests {
568    use super::*;
569
570    #[test]
571    fn test_insert_and_get() {
572        let map = HashMap::new();
573        assert_eq!(map.insert(1, 100), None);
574        assert_eq!(map.get(&1), Some(100));
575        assert_eq!(map.get(&2), None);
576    }
577
578    #[test]
579    fn test_insert_replace() {
580        let map = HashMap::new();
581        assert_eq!(map.insert(1, 100), None);
582        assert_eq!(map.insert(1, 200), Some(100));
583        assert_eq!(map.get(&1), Some(200));
584    }
585
586    #[test]
587    fn test_concurrent_inserts() {
588        use alloc::sync::Arc;
589        extern crate std;
590        use std::thread;
591
592        let map = Arc::new(HashMap::new());
593        let mut handles = alloc::vec::Vec::new();
594
595        for thread_id in 0..4 {
596            let map_clone = Arc::clone(&map);
597            let handle = thread::spawn(move || {
598                for i in 0..1000 {
599                    let key = thread_id * 1000 + i;
600                    map_clone.insert(key, key * 2);
601                }
602            });
603            handles.push(handle);
604        }
605
606        for handle in handles {
607            handle.join().unwrap();
608        }
609
610        for thread_id in 0..4 {
611            for i in 0..1000 {
612                let key = thread_id * 1000 + i;
613                assert_eq!(map.get(&key), Some(key * 2));
614            }
615        }
616    }
617}