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;