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