Skip to main content

kovan_map/
hopscotch.rs

1//! Lock-Free Growing/Shrinking Hopscotch Hash Map
2//!
3//! # Features
4//!
5//! - **Lock-free reads**: a lookup and a walk take no guard and never wait for a writer.
6//! - **Home-bucket writer guards**: every write that links, replaces, unlinks or moves an entry
7//!   holds the writer guard of the entry's home bucket, so a writer's scan of its home is stable
8//!   and a key never has two entries.
9//! - **Displacement without a gap**: an insert whose neighborhood is full moves entries of other
10//!   homes (the same allocation, not a copy) under their home guards. A moved entry is linked at
11//!   its new slot before it is unlinked from the old one, and its home's move stamp advances in
12//!   between, so a lookup that raced the move rescans instead of missing the key, and a walk
13//!   meets the entry and yields it once.
14//! - **Safe Resizing**: A resize (and a clear) takes every home bucket's writer guard of the
15//!   table before it copies a slot, so a write either lands before the copy (and is copied) or
16//!   waits for the new table; a table a resize replaced never admits a writer again. An insert
17//!   that landed is final: it never retries into the new table, where it would meet its own
18//!   migrated entry and report it as another caller's.
19//! - **Stable iteration**: An iterator walks the table that was current when it was created to
20//!   the end, even when a resize replaces it meanwhile.
21//! - **Memory reclamation**: Uses Kovan.
22//! - **Clone Support**: Supports V: Clone (e.g., `Arc<T>`) instead of just Copy.
23
24extern crate alloc;
25
26use crate::sync::spin_hint;
27use crate::sync::{AtomicBool, AtomicUsize};
28use alloc::boxed::Box;
29use core::borrow::Borrow;
30use core::hash::{BuildHasher, Hash};
31use core::sync::atomic::Ordering;
32use displace::{InsertResult, Pending};
33use foldhash::fast::FixedState;
34use kovan::{Atomic, CachePadded, pin, retire};
35use table::{HOP_MASK, HomeGuard, Table, Word, hop_bits};
36
37pub use iter::{HopscotchIntoIter, HopscotchIter, HopscotchKeys, HopscotchValues};
38
39mod conditional;
40mod displace;
41mod iter;
42mod resize;
43mod std_traits;
44mod table;
45
46/// Neighborhood size (H parameter in hopscotch hashing)
47const NEIGHBORHOOD_SIZE: usize = 32;
48
49/// Initial capacity
50const INITIAL_CAPACITY: usize = 64;
51
52/// Whether `count` entries overfill a table of `capacity`: more than three quarters full.
53#[inline(always)]
54fn overfull(count: usize, capacity: usize) -> bool {
55    4 * count > 3 * capacity
56}
57
58/// Whether `count` entries underfill a table of `capacity`: less than a quarter full, and the
59/// table larger than the least one.
60#[inline(always)]
61fn underfull(count: usize, capacity: usize) -> bool {
62    4 * count < capacity && capacity > MIN_CAPACITY
63}
64
65/// Minimum capacity to prevent excessive shrinking
66const MIN_CAPACITY: usize = 64;
67
68/// Maximum probe distance before giving up and resizing
69const MAX_PROBE_DISTANCE: usize = 512;
70
71/// A concurrent, lock-free hash map based on Hopscotch Hashing.
72pub struct HopscotchMap<K: 'static, V: 'static, S = FixedState> {
73    table: Atomic<Table<K, V>>,
74    /// The entries in the map, on a cache line of its own: every write that links or unlinks an
75    /// entry bumps it, and every operation reads the table pointer and the resize flag, which
76    /// would otherwise share its line.
77    count: CachePadded<AtomicUsize>,
78    /// Prevents concurrent writes during resize migration to avoid lost updates
79    resizing: AtomicBool,
80    hasher: S,
81}
82
83/// How a write of `insert_impl` ended.
84enum Outcome<R, V> {
85    /// The key was absent: this call linked its entry; what the caller asked of its value.
86    Linked(R),
87    /// The key was present and this call replaced its entry: the old value.
88    Replaced(V),
89    /// The key was present and this call only claims an absent key: its value.
90    Present(V),
91}
92
93/// A writer's table and the home guard it holds there ([`HopscotchMap::home_of`]).
94type HeldHome<'g, K, V> = (&'g Table<K, V>, HomeGuard<'g>);
95
96/// What kept a writer from its key's home guard ([`HopscotchMap::home_of`]).
97enum Blocked<'g, K, V> {
98    /// A resize or a clear is in flight: the writer waits for it and reloads the table.
99    Resizing,
100    /// The home holds no entry: its hop bits were clear in the one read of its control word.
101    Vacant,
102    /// Another writer holds the home's guard in this table.
103    Held(&'g Table<K, V>),
104}
105
106// Small accessors that never hash: only the struct's own `'static` bound, as std's equivalent
107// block for `with_hasher`/`with_capacity_and_hasher`/`capacity`/`len`/`is_empty`/`hasher` needs
108// no `Hash`, `Eq`, `Clone` or `BuildHasher`. A method that hashes or clones a value lives in the
109// bound impl block below instead.
110impl<K: 'static, V: 'static, S> HopscotchMap<K, V, S> {
111    /// Creates a new `HopscotchMap` with the specified hasher and default capacity.
112    pub fn with_hasher(hasher: S) -> Self {
113        Self::with_capacity_and_hasher(INITIAL_CAPACITY, hasher)
114    }
115
116    /// Creates a new `HopscotchMap` with the specified capacity and hasher.
117    pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self {
118        let table = Table::new(capacity);
119        Self {
120            table: Atomic::new(Box::into_raw(Box::new(table))),
121            count: CachePadded::new(AtomicUsize::new(0)),
122            resizing: AtomicBool::new(false),
123            hasher,
124        }
125    }
126
127    /// Returns the number of elements in the map.
128    pub fn len(&self) -> usize {
129        self.count.load(Ordering::Relaxed)
130    }
131
132    /// Returns `true` if the map contains no elements.
133    pub fn is_empty(&self) -> bool {
134        self.len() == 0
135    }
136
137    /// Returns the current capacity of the map.
138    pub fn capacity(&self) -> usize {
139        let guard = pin();
140        let table_ptr = self.table.load(Ordering::Acquire, &guard);
141        unsafe { (*table_ptr.as_raw()).capacity }
142    }
143
144    /// Get the underlying hasher.
145    pub fn hasher(&self) -> &S {
146        &self.hasher
147    }
148}
149
150// Construction with the built-in hasher never hashes either: the struct's own `'static` only,
151// as std's `HashMap::new`/`with_capacity` carry no bound.
152#[cfg(feature = "std")]
153impl<K: 'static, V: 'static> HopscotchMap<K, V, FixedState> {
154    /// Creates a new `HopscotchMap` with default capacity and hasher.
155    pub fn new() -> Self {
156        Self::with_hasher(FixedState::default())
157    }
158
159    /// Creates a new `HopscotchMap` with the specified capacity and default hasher.
160    pub fn with_capacity(capacity: usize) -> Self {
161        Self::with_capacity_and_hasher(capacity, FixedState::default())
162    }
163}
164
165impl<K, V, S> HopscotchMap<K, V, S>
166where
167    K: Hash + Eq + Clone + 'static,
168    V: Clone + 'static,
169    S: BuildHasher,
170{
171    #[inline]
172    fn wait_for_resize(&self) {
173        while self.resizing.load(Ordering::Acquire) {
174            spin_hint();
175        }
176    }
177
178    /// Returns the value corresponding to the key.
179    pub fn get<Q>(&self, key: &Q) -> Option<V>
180    where
181        K: Borrow<Q>,
182        Q: Hash + Eq + ?Sized,
183    {
184        self.get_hashed(self.hasher.hash_one(key), key)
185    }
186
187    /// [`get`](Self::get) of a key whose hash the caller already took.
188    #[inline(always)]
189    fn get_hashed<Q>(&self, hash: u64, key: &Q) -> Option<V>
190    where
191        K: Borrow<Q>,
192        Q: Eq + ?Sized,
193    {
194        let guard = pin();
195        let table_ptr = self.table.load(Ordering::Acquire, &guard);
196        let table = unsafe { &*table_ptr.as_raw() };
197
198        table
199            .lookup(hash, key, &guard)
200            .map(|entry| entry.value.clone())
201    }
202
203    /// Inserts a key-value pair into the map, returning the value it replaced.
204    ///
205    /// Linearizable at the write under the key's home guard; the answer is
206    /// exactly the value that write replaced.
207    pub fn insert(&self, key: K, value: V) -> Option<V> {
208        let hash = self.hasher.hash_one(&key);
209        match self.insert_impl(hash, key, value, false, |_| ()) {
210            Outcome::Linked(()) => None,
211            Outcome::Replaced(old) | Outcome::Present(old) => Some(old),
212        }
213    }
214
215    /// The writer guard of the home of `hash` in the current table, taken without waiting: the
216    /// first step of every write. `Err` when a resize is in flight, when the home holds no entry
217    /// and `vacant_answers` (seeing that takes no guard: an entry's hop bit stays set from its
218    /// publication to its unlink, and one read of the home's control word serves this test, the
219    /// test of the guard and the compare-exchange that takes it), or when another writer holds
220    /// the guard.
221    ///
222    /// A resize takes every home guard of the table before it copies a slot (`hold_writers`)
223    /// and keeps the guards of a table it replaced, so holding this guard means the table is live
224    /// and every slot the holder writes is copied by any resize that follows. No lock-order
225    /// deadlock: a guard holder never waits for anything (it takes the guards of other homes it
226    /// displaces entries of without waiting).
227    #[inline(always)]
228    fn home_of<'g>(
229        &self,
230        hash: u64,
231        guard: &'g kovan::Guard,
232        vacant_answers: bool,
233    ) -> Result<HeldHome<'g, K, V>, Blocked<'g, K, V>> {
234        let table_ptr = self.table.load(Ordering::Acquire, guard);
235        // SAFETY: loaded under `guard`, which keeps the table from being freed.
236        let table = unsafe { &*table_ptr.as_raw() };
237        if self.resizing.load(Ordering::Acquire) {
238            return Err(Blocked::Resizing);
239        }
240        match table.home_guard_unless(table.bucket_index(hash), |word| {
241            vacant_answers && hop_bits(word) == 0
242        }) {
243            Ok(home) => Ok((table, home)),
244            Err(word) if vacant_answers && hop_bits(word) == 0 => Err(Blocked::Vacant),
245            Err(_) => Err(Blocked::Held(table)),
246        }
247    }
248
249    /// Count in an entry this call linked, before its home guard is released: concurrent removes
250    /// cannot decrement the count below the true entry count, and a clear (which resets the count
251    /// while it holds every home guard) never sees the entry without its count. The capacity to
252    /// grow the table to when it is more than three quarters full.
253    #[inline(always)]
254    fn count_in(&self, table: &Table<K, V>) -> Option<usize> {
255        let count = self.count.fetch_add(1, Ordering::Relaxed) + 1;
256        overfull(count, table.capacity).then_some(table.capacity * 2)
257    }
258
259    /// Unlink the entry in the slot `offset` past the home `home` holds, and count it out before
260    /// the guard is released: the capacity to shrink the table to when it is less than a quarter
261    /// full. The caller retires the entry once it released the guard.
262    #[inline(always)]
263    fn unlink_held(
264        &self,
265        table: &Table<K, V>,
266        home: &mut HomeGuard<'_>,
267        offset: usize,
268    ) -> Option<usize> {
269        // A store, not a CAS: no other thread writes an occupied slot of a home whose guard this
270        // call holds. Release: a reader that acquires the free slot sees everything this call
271        // wrote before it.
272        table
273            .get_bucket(home.idx + offset)
274            .store(Word::free(), Ordering::Release);
275        home.stage_unlinked(offset);
276        // Counted down before the home guard is released, as an insert counts up: a clear
277        // resets the count while it holds every home guard, so no decrement for an entry it
278        // already cleared lands after the reset and eats the count of a later insert. One
279        // subtract, no saturation: the entry this call unlinked was counted when it was linked
280        // (before its home guard was released) and no clear ran since (a clear holds every home
281        // guard, this one included), so the count is at least one here.
282        let prev = self.count.fetch_sub(1, Ordering::Relaxed);
283        debug_assert!(prev > 0, "an unlinked entry was never counted");
284        underfull(prev - 1, table.capacity).then_some(table.capacity / 2)
285    }
286
287    /// The one write path of `insert`, `insert_if_absent` and `get_or_insert`: link the key's
288    /// entry, or (unless `only_if_absent`) replace the present one, under the key's home guard.
289    /// `on_insert` reads the value this call linked.
290    fn insert_impl<R>(
291        &self,
292        hash: u64,
293        key: K,
294        value: V,
295        only_if_absent: bool,
296        on_insert: impl FnOnce(&V) -> R,
297    ) -> Outcome<R, V> {
298        let mut pending = Pending::Parts { hash, key, value };
299
300        loop {
301            self.wait_for_resize();
302
303            let guard = pin();
304            // The home guard serializes the writes of one home, so the existence scan and the
305            // slot claim inside try_insert are one atomic step, and no displacement moves an
306            // entry of the home meanwhile. A resize in flight sends the loop back to wait for
307            // it; a held guard, to spin and reload the table.
308            let (table, mut home) = match self.home_of(hash, &guard, false) {
309                Ok(held) => held,
310                Err(Blocked::Held(table)) => {
311                    #[cfg(test)]
312                    pause::at(pause::Point::WriterMetHeldGuard);
313                    // A claim answers a key the holder linked meanwhile without waiting for the
314                    // guard, from the same lookup.
315                    if only_if_absent && let Some(entry) = table.lookup(hash, pending.key(), &guard)
316                    {
317                        return Outcome::Present(entry.value.clone());
318                    }
319                    spin_hint();
320                    continue;
321                }
322                Err(_) => continue,
323            };
324
325            let outcome = Self::try_insert(table, &mut home, pending, only_if_absent, &guard);
326            let grow_to = match outcome {
327                InsertResult::Linked(_) => self.count_in(table),
328                _ => None,
329            };
330            // The guard is released (publishing the new entry's hop bit) before the resize arms
331            // below drop the pin and migrate: the release touches this table's bucket, which is
332            // only safe while the pin keeps the table alive.
333            drop(home);
334            match outcome {
335                InsertResult::Linked(entry) => {
336                    #[cfg(test)]
337                    pause::at(pause::Point::AfterLanding);
338                    // Final. The write landed in a live table under its home
339                    // guard, so a resize that starts after it copies it; a
340                    // retry here would meet this call's own migrated entry
341                    // and report it as present (`insert_if_absent` answering
342                    // `Some(own value)` for an insert that happened).
343                    // SAFETY: linked by this call under `guard`, which keeps it (and its table)
344                    // from being freed even if a writer unlinks it now.
345                    let answer = on_insert(unsafe { &(*entry).value });
346                    if let Some(capacity) = grow_to {
347                        drop(guard);
348                        self.try_resize(capacity);
349                    }
350                    return Outcome::Linked(answer);
351                }
352                InsertResult::Replaced(old) => return Outcome::Replaced(old),
353                InsertResult::Exists(existing) => return Outcome::Present(existing),
354                InsertResult::NeedResize(back) => {
355                    pending = back;
356                    let current_capacity = table.capacity;
357                    drop(guard);
358                    self.try_resize(current_capacity * 2);
359                }
360                InsertResult::Retry(back) => {
361                    pending = back;
362                    spin_hint();
363                }
364            }
365        }
366    }
367
368    /// Returns the value corresponding to the key, or inserts the given value if the key is not present.
369    ///
370    /// Linearizable and exact: of the callers racing for an absent key, the
371    /// one whose write links the key's entry (under the key's home guard)
372    /// gets its own value back, and every other gets the value it found
373    /// there (the winner's, unless a later write already replaced it). A
374    /// present key is answered by a lookup that takes no guard.
375    pub fn get_or_insert(&self, key: K, value: V) -> V {
376        // A present key is answered by the lookup `get` makes, before any write step: this call
377        // writes nothing then, so it takes effect at that lookup. The hash is taken once.
378        let hash = self.hasher.hash_one(&key);
379        if let Some(v) = self.get_hashed(hash, &key) {
380            return v;
381        }
382        match self.insert_impl(hash, key, value, true, V::clone) {
383            Outcome::Linked(own) | Outcome::Present(own) | Outcome::Replaced(own) => own,
384        }
385    }
386
387    /// Insert a key-value pair only if the key does not exist.
388    /// Returns `None` if inserted, `Some(existing_value)` if the key already exists.
389    ///
390    /// Exact: `None` exactly when this call linked the key's entry, `Some`
391    /// with the value it found under the key's home guard otherwise; never
392    /// both, and a resize in flight changes neither (a write that landed is
393    /// final). A call that finds the key present drops its own key and value,
394    /// once.
395    pub fn insert_if_absent(&self, key: K, value: V) -> Option<V> {
396        // As `get_or_insert`: a present key is answered by a lookup, with the one hash.
397        let hash = self.hasher.hash_one(&key);
398        if let Some(existing) = self.get_hashed(hash, &key) {
399            return Some(existing);
400        }
401        match self.insert_impl(hash, key, value, true, |_| ()) {
402            Outcome::Linked(()) => None,
403            Outcome::Present(existing) | Outcome::Replaced(existing) => Some(existing),
404        }
405    }
406
407    /// Remove **all** nodes matching `key`, returning the most recent value
408    /// if the key was present.
409    ///
410    /// Every write of a home runs under its writer guard, so a key has at
411    /// most one entry and this removes what [`remove`](Self::remove)
412    /// removes. It keeps removing until a scan finds no match, so the key is
413    /// absent at the linearization point of the final scan, and it is kept
414    /// for parity with `HashMap::force_remove`.
415    ///
416    /// Note: a concurrent `insert` of the same key can land after the final
417    /// scan, as with any removal under contention.
418    pub fn force_remove<Q>(&self, key: &Q) -> Option<V>
419    where
420        K: Borrow<Q>,
421        Q: Hash + Eq + ?Sized,
422    {
423        let mut newest = None;
424        loop {
425            match self.remove(key) {
426                Some(v) => {
427                    // The first removal unlinks the first match in scan
428                    // order - the live (most recent) version.
429                    if newest.is_none() {
430                        newest = Some(v);
431                    }
432                }
433                None => return newest,
434            }
435        }
436    }
437
438    /// Removes a key from the map, returning the value at the key if the key was previously in the map.
439    ///
440    /// Linearizable at the unlink under the key's home guard: [`remove_if`](Self::remove_if)
441    /// with a predicate that always holds, one write path for both.
442    pub fn remove<Q>(&self, key: &Q) -> Option<V>
443    where
444        K: Borrow<Q>,
445        Q: Hash + Eq + ?Sized,
446    {
447        self.remove_where(key, |_| true)
448    }
449
450    /// Clears the map, removing all key-value pairs.
451    pub fn clear(&self) {
452        while self
453            .resizing
454            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
455            .is_err()
456        {
457            spin_hint();
458        }
459
460        let guard = pin();
461        let table_ptr = self.table.load(Ordering::Acquire, &guard);
462        let table = unsafe { &*table_ptr.as_raw() };
463        // No insert is between its scan and its claim while the slots are
464        // cleared: one that landed is cleared with the rest, one that did
465        // not lands after the clear with its hop bit intact. No move has an
466        // entry in two slots either (a move holds its entry's home guard).
467        Self::hold_writers(table);
468
469        for i in 0..(table.capacity + NEIGHBORHOOD_SIZE) {
470            let bucket = table.get_bucket(i);
471            let word = bucket.load(Ordering::Acquire, &guard);
472
473            if !word.is_free()
474                && bucket.replace(
475                    word,
476                    Word::free(),
477                    Ordering::Release,
478                    Ordering::Relaxed,
479                    &guard,
480                )
481            {
482                unsafe { retire(word.ptr()) };
483            }
484
485            if i < table.capacity {
486                // Keeps the guard (held by this clear) and the move stamp.
487                let b = table.get_bucket(i);
488                b.control.fetch_and(!HOP_MASK, Ordering::Release);
489            }
490        }
491
492        self.count.store(0, Ordering::Release);
493        Self::release_writers(table);
494        self.resizing.store(false, Ordering::Release);
495    }
496
497    /// Returns `true` if the key is present.
498    pub fn contains_key<Q>(&self, key: &Q) -> bool
499    where
500        K: Borrow<Q>,
501        Q: Hash + Eq + ?Sized,
502    {
503        self.get(key).is_some()
504    }
505
506    /// Insert all `(K, V)` pairs from `iter`. Takes `&self` (concurrent map).
507    pub fn extend<I: IntoIterator<Item = (K, V)>>(&self, iter: I) {
508        for (k, v) in iter {
509            self.insert(k, v);
510        }
511    }
512}
513
514unsafe impl<K: Send, V: Send, S: Send> Send for HopscotchMap<K, V, S> {}
515// SAFETY: Shared references allow moving K and V across threads (via insert/remove),
516// so K and V must be Send in addition to Sync.
517unsafe impl<K: Send + Sync, V: Send + Sync, S: Send + Sync> Sync for HopscotchMap<K, V, S> {}
518
519impl<K, V, S> Drop for HopscotchMap<K, V, S> {
520    fn drop(&mut self) {
521        // SAFETY: `drop(&mut self)` guarantees exclusive ownership - no concurrent
522        // readers can exist. The Table's destructor frees the remaining entries.
523        let guard = pin();
524        let table_ptr = self.table.load(Ordering::Acquire, &guard);
525
526        unsafe {
527            drop(Box::from_raw(table_ptr.as_raw()));
528        }
529
530        // Flush nodes previously retired by concurrent operations (insert/remove/resize)
531        // to prevent use-after-free during process teardown.
532        drop(guard);
533        kovan::flush();
534    }
535}
536
537#[cfg(test)]
538mod pause;
539
540#[cfg(test)]
541mod tests;