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