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