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;