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