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;