xenium
vyukov_hash_map.hpp
1 //
2 // Copyright (c) 2018-2020 Manuel Pöter.
3 // Licensed under the MIT License. See LICENSE file in the project root for full license information.
4 //
5 
6 #ifndef XENIUM_VYUKOV_HASH_MAP_IMPL
7  #error "This is an impl file and must not be included directly!"
8 #endif
9 
10 #include <xenium/acquire_guard.hpp>
11 #include <xenium/backoff.hpp>
12 #include <xenium/parameter.hpp>
13 #include <xenium/policy.hpp>
14 
15 #include <atomic>
16 #include <cassert>
17 #include <cstring>
18 
19 #ifdef _MSC_VER
20  #pragma warning(push)
21  #pragma warning(disable : 26495) // uninitialized member variable
22  #pragma warning(disable : 4324) // structure was padded due to alignment specifier
23 #endif
24 
25 namespace xenium {
26 
27 template <class Key, class Value, class... Policies>
28 struct vyukov_hash_map<Key, Value, Policies...>::bucket_state {
29  bucket_state() = default;
30 
31  [[nodiscard]] bucket_state locked() const noexcept { return bucket_state(value | lock); }
32  [[nodiscard]] bucket_state clear_lock() const {
33  assert(value & lock);
34  return bucket_state(value ^ lock);
35  }
36  [[nodiscard]] bucket_state new_version() const noexcept { return bucket_state(value + version_inc); }
37  [[nodiscard]] bucket_state inc_item_count() const {
38  assert(item_count() < bucket_item_count);
39  bucket_state result(value + item_count_inc);
40  assert(result.item_count() == item_count() + 1);
41  return result;
42  }
43  [[nodiscard]] bucket_state dec_item_count() const {
44  assert(item_count() > 0);
45  bucket_state result(value - item_count_inc);
46  assert(result.item_count() == item_count() - 1);
47  return result;
48  }
49  [[nodiscard]] bucket_state set_delete_marker(std::uint32_t marker) const {
50  assert(delete_marker() == 0);
51  bucket_state result(value | (marker << delete_marker_shift));
52  assert(result.delete_marker() == marker);
53  return result;
54  }
55 
56  bool operator==(bucket_state r) const noexcept { return this->value == r.value; }
57  bool operator!=(bucket_state r) const noexcept { return this->value != r.value; }
58 
59  [[nodiscard]] std::uint32_t item_count() const noexcept { return (value >> 1) & item_count_mask; }
60  [[nodiscard]] std::uint32_t delete_marker() const noexcept {
61  return (value >> delete_marker_shift) & item_count_mask;
62  }
63  [[nodiscard]] std::uint32_t version() const noexcept { return value >> version_shift; }
64  [[nodiscard]] bool is_locked() const noexcept { return (value & lock) != 0; }
65 
66 private:
67  explicit bucket_state(std::uint32_t value) noexcept : value(value) {}
68 
69  std::uint32_t value{};
70 
71  /*
72  value has the same structure as the following bit field:
73  struct {
74  // the lock bit
75  unsigned lock : 1;
76 
77  // the number of items in the bucket array
78  unsigned item_count : find_last_bit_set(bucket_item_count);
79 
80  // marker for the item that is currently beeing removed
81  unsigned delete_marker : find_last_bit_set(bucket_item_count);
82 
83  // version counter - gets incremented at the end of every remove operation
84  unsigned version : sizeof(unsigned) * 8 - 2 * find_last_bit_set(bucket_item_count) - 1;
85  };
86  */
87 
88  static constexpr std::size_t item_counter_bits = utils::find_last_bit_set(bucket_item_count);
89 
90  static constexpr std::size_t item_count_shift = 1;
91  static constexpr std::size_t delete_marker_shift = item_count_shift + item_counter_bits;
92  static constexpr std::size_t version_shift = delete_marker_shift + item_counter_bits;
93 
94  static constexpr std::uint32_t lock = 1;
95  static constexpr std::uint32_t version_inc = 1 << version_shift;
96  static constexpr std::uint32_t item_count_inc = 1 << item_count_shift;
97 
98  static constexpr std::uint32_t item_count_mask = (1 << item_counter_bits) - 1;
99 };
100 
101 template <class Key, class Value, class... Policies>
102 struct vyukov_hash_map<Key, Value, Policies...>::bucket {
103  std::atomic<bucket_state> state;
104  std::atomic<extension_item*> head;
105  typename traits::storage_key_type key[bucket_item_count];
106  typename traits::storage_value_type value[bucket_item_count];
107 };
108 
109 template <class Key, class Value, class... Policies>
110 struct vyukov_hash_map<Key, Value, Policies...>::extension_item {
111  typename traits::storage_key_type key;
112  typename traits::storage_value_type value;
113  std::atomic<extension_item*> next;
114 };
115 
116 template <class Key, class Value, class... Policies>
117 struct vyukov_hash_map<Key, Value, Policies...>::extension_bucket {
118  std::atomic<std::uint32_t> lock;
119  std::atomic<extension_item*> head;
120  extension_item items[extension_item_count];
121 
122  void acquire_lock() {
123  backoff backoff;
124  for (;;) {
125  while (lock.load(std::memory_order_relaxed) != 0) {
126  ;
127  }
128  // (1) - this acquire-exchange synchronizes-with the release-store (2)
129  if (lock.exchange(1, std::memory_order_acquire) == 0) {
130  return;
131  }
132  backoff();
133  }
134  }
135  void release_lock() {
136  // (2) - this release-store synchronizes-with the acquire-exchange (1)
137  lock.store(0, std::memory_order_release);
138  }
139 };
140 
141 template <class Key, class Value, class... Policies>
142 struct alignas(64) vyukov_hash_map<Key, Value, Policies...>::block : reclaimer::template enable_concurrent_ptr<block> {
143  std::uint32_t mask;
144  std::uint32_t bucket_count;
145  std::uint32_t extension_bucket_count;
146  extension_bucket* extension_buckets;
147 
148  // TODO - adapt to be customizable via map_to_bucket policy
149  [[nodiscard]] std::uint32_t index(const key_type& key) const { return static_cast<std::uint32_t>(key & mask); }
150  bucket* buckets() { return reinterpret_cast<bucket*>(this + 1); }
151 
152  void operator delete(void* p) { ::operator delete(p, cacheline_size); } // NOLINT (new-delete-overloads)
153 };
154 
155 template <class Key, class Value, class... Policies>
156 struct vyukov_hash_map<Key, Value, Policies...>::unlocker {
157  unlocker(bucket& locked_bucket, bucket_state state) : state(state), locked_bucket(locked_bucket) {}
158  ~unlocker() {
159  if (enabled) {
160  assert(locked_bucket.state.load().is_locked());
161  locked_bucket.state.store(state, std::memory_order_relaxed);
162  }
163  }
164  void unlock(bucket_state new_state, std::memory_order order) {
165  assert(enabled);
166  assert(locked_bucket.state.load().is_locked());
167  locked_bucket.state.store(new_state, order);
168  enabled = false;
169  }
170  void disable() { enabled = false; }
171 
172 private:
173  bool enabled = true;
174  bucket_state state;
175  bucket& locked_bucket;
176 };
177 
178 template <class Key, class Value, class... Policies>
179 vyukov_hash_map<Key, Value, Policies...>::vyukov_hash_map(std::size_t initial_capacity) : resize_lock(0) {
180  auto b = allocate_block(static_cast<std::uint32_t>(utils::next_power_of_two(initial_capacity)));
181  if (b == nullptr) {
182  throw std::bad_alloc();
183  }
184  data_block.store(b, std::memory_order_relaxed);
185 }
186 
187 template <class Key, class Value, class... Policies>
188 vyukov_hash_map<Key, Value, Policies...>::~vyukov_hash_map() {
189  // delete all remaining entries - this also reclaims any internally allocated
190  // nodes as well as managed_ptr instances.
191  // This could be implemented in a more efficient way, but it works for now!
192  try {
193  auto it = begin();
194  while (it != end()) {
195  try {
196  erase(it);
197  } catch(...) {
198  }
199  }
200  } catch(...) {
201  }
202  delete data_block.load().get();
203 }
204 
205 template <class Key, class Value, class... Policies>
206 bool vyukov_hash_map<Key, Value, Policies...>::emplace(key_type key, value_type value) {
207  return do_get_or_emplace<false>(
208  std::move(key), [v = std::move(value)]() mutable { return std::move(v); }, [](accessor&&, auto&) {});
209 }
210 
211 template <class Key, class Value, class... Policies>
212 template <class... Args>
213 auto vyukov_hash_map<Key, Value, Policies...>::get_or_emplace(key_type key, Args&&... args)
214  -> std::pair<accessor, bool> {
215  std::pair<accessor, bool> result;
216  result.second = do_get_or_emplace<true>(
217  std::move(key),
218  [&] { return value_type(std::forward<Args>(args)...); },
219  [&result](accessor&& acc, auto&) { result.first = std::move(acc); });
220  return result;
221 }
222 
223 template <class Key, class Value, class... Policies>
224 template <class Factory>
225 auto vyukov_hash_map<Key, Value, Policies...>::get_or_emplace_lazy(key_type key, Factory&& factory)
226  -> std::pair<accessor, bool> {
227  std::pair<accessor, bool> result;
228  result.second = do_get_or_emplace<true>(std::move(key),
229  std::forward<Factory>(factory),
230  [&result](accessor&& acc, auto&) { result.first = std::move(acc); });
231  return result;
232 }
233 
234 template <class Key, class Value, class... Policies>
235 template <bool AcquireAccessor, class Factory, class Callback>
236 bool vyukov_hash_map<Key, Value, Policies...>::do_get_or_emplace(Key&& key, Factory&& factory, Callback&& callback) {
237  const hash_t h = hash{}(key);
238 
239  accessor acc;
240 retry:
241  guarded_block b;
242  bucket_state state;
243  bucket& bucket = lock_bucket(h, b, state);
244 
245  unlocker unlocker(bucket, state);
246 
247  std::uint32_t item_count = state.item_count();
248 
249  for (std::uint32_t i = 0; i != item_count; ++i) {
250  if (traits::template compare_key<AcquireAccessor>(bucket.key[i], bucket.value[i], key, h, acc)) {
251  callback(std::move(acc), bucket.value[i]);
252  unlocker.unlock(state, std::memory_order_relaxed);
253  return false;
254  }
255  }
256 
257  if (item_count < bucket_item_count) {
258  traits::template store_item<AcquireAccessor>(
259  bucket.key[item_count], bucket.value[item_count], h, std::move(key), factory(), std::memory_order_relaxed, acc);
260  callback(std::move(acc), bucket.value[item_count]);
261  // release the bucket lock and increment the item count
262  // (3) - this release-store synchronizes-with the acquire-CAS (7, 30, 34, 37) and the acquire-load (23)
263  unlocker.unlock(state.inc_item_count(), std::memory_order_release);
264  return true;
265  }
266 
267  // TODO - keep extension items sorted
268  for (extension_item* extension = bucket.head.load(std::memory_order_relaxed); extension != nullptr;
269  extension = extension->next.load(std::memory_order_relaxed)) {
270  if (traits::template compare_key<AcquireAccessor>(extension->key, extension->value, key, h, acc)) {
271  callback(std::move(acc), extension->value);
272  unlocker.unlock(state, std::memory_order_relaxed);
273  return false;
274  }
275  }
276 
277  extension_item* extension = allocate_extension_item(b.get(), h);
278  if (extension == nullptr) {
279  unlocker.disable(); // bucket is unlocked in grow()
280  grow(bucket, state);
281  goto retry;
282  }
283  try {
284  traits::template store_item<AcquireAccessor>(
285  extension->key, extension->value, h, std::move(key), factory(), std::memory_order_relaxed, acc);
286  } catch (...) {
287  free_extension_item(extension);
288  throw;
289  }
290  callback(std::move(acc), extension->value);
291  auto old_head = bucket.head.load(std::memory_order_relaxed);
292  extension->next.store(old_head, std::memory_order_relaxed);
293  // (4) - this release-store synchronizes-with the acquire-load (25)
294  bucket.head.store(extension, std::memory_order_release);
295  // release the bucket lock
296  // (5) - this release-store synchronizes-with the acquire-CAS (7, 30, 34, 37) and the acquire-load (23)
297  unlocker.unlock(state, std::memory_order_release);
298 
299  return true;
300 }
301 
302 template <class Key, class Value, class... Policies>
304  accessor acc;
305  bool result = do_extract(key, acc);
306  traits::reclaim(acc);
307  return result;
308 }
309 
310 template <class Key, class Value, class... Policies>
311 bool vyukov_hash_map<Key, Value, Policies...>::extract(const key_type& key, accessor& acc) {
312  bool result = do_extract(key, acc);
313  traits::reclaim_internal(acc);
314  return result;
315 }
316 
317 template <class Key, class Value, class... Policies>
318 bool vyukov_hash_map<Key, Value, Policies...>::do_extract(const key_type& key, accessor& result) {
319  const hash_t h = hash{}(key);
320  backoff backoff;
321  guarded_block b;
322 
323 restart:
324  // (6) - this acquire-load synchronizes-with the release-store (31)
325  b.acquire(data_block, std::memory_order_acquire);
326  const std::size_t bucket_idx = h & b->mask;
327  bucket& bucket = b->buckets()[bucket_idx];
328  bucket_state state = bucket.state.load(std::memory_order_relaxed);
329  std::uint32_t item_count = state.item_count();
330 
331  if (item_count == 0) {
332  return false; // no items to check
333  }
334 
335  if (state.is_locked()) {
336  goto restart;
337  }
338 
339  auto locked_state = state.locked();
340  // (7) - this acquire-CAS synchronizes-with the release-store (3, 5, 11, 13, 14, 36, 38)
341  if (!bucket.state.compare_exchange_strong(
342  state, locked_state, std::memory_order_acquire, std::memory_order_relaxed)) {
343  backoff();
344  goto restart;
345  }
346 
347  unlocker unlocker(bucket, state);
348 
349  // we have the lock - now look for the key
350 
351  for (std::uint32_t i = 0; i != item_count; ++i) {
352  if (traits::template compare_key<true>(bucket.key[i], bucket.value[i], key, h, result)) {
353  extension_item* extension = bucket.head.load(std::memory_order_relaxed);
354  if (extension) {
355  // signal which item we are deleting
356  bucket.state.store(locked_state.set_delete_marker(i + 1), std::memory_order_relaxed);
357 
358  auto k = extension->key.load(std::memory_order_relaxed);
359  auto v = extension->value.load(std::memory_order_relaxed);
360  bucket.key[i].store(k, std::memory_order_relaxed);
361  // (8) - this release-store synchronizes-with the acquire-load (24)
362  bucket.value[i].store(v, std::memory_order_release);
363 
364  // reset the delete marker
365  locked_state = locked_state.new_version();
366  // (9) - this release-store synchronizes-with the acquire-load (23)
367  bucket.state.store(locked_state, std::memory_order_release);
368 
369  extension_item* extension_next = extension->next.load(std::memory_order_relaxed);
370  // (10) - this release-store synchronizes-with the acquire-load (25)
371  bucket.head.store(extension_next, std::memory_order_release);
372 
373  // release the bucket lock and increase the version
374  // (11) - this release-store synchronizes-with the acquire-CAS (7, 30, 34, 37) and the acquire-load (23)
375  unlocker.unlock(locked_state.new_version().clear_lock(), std::memory_order_release);
376 
377  free_extension_item(extension);
378  } else {
379  if (i != item_count - 1) {
380  // signal which item we are deleting
381  bucket.state.store(locked_state.set_delete_marker(i + 1), std::memory_order_relaxed);
382 
383  auto k = bucket.key[item_count - 1].load(std::memory_order_relaxed);
384  auto v = bucket.value[item_count - 1].load(std::memory_order_relaxed);
385  bucket.key[i].store(k, std::memory_order_relaxed);
386  // (12) - this release-store synchronizes-with the acquire-load (24)
387  bucket.value[i].store(v, std::memory_order_release);
388  }
389 
390  // release the bucket lock, reset the delete marker (if it is set), increase the version
391  // and decrement the item counter.
392  // (13) - this release-store synchronizes-with the acquire-CAS (7, 30, 34, 37) and the acquire-load (23)
393  unlocker.unlock(state.new_version().dec_item_count(), std::memory_order_release);
394  }
395  return true;
396  }
397  }
398 
399  auto extension_prev = &bucket.head;
400  extension_item* extension = extension_prev->load(std::memory_order_relaxed);
401  while (extension) {
402  if (traits::template compare_key<true>(extension->key, extension->value, key, h, result)) {
403  extension_item* extension_next = extension->next.load(std::memory_order_relaxed);
404  extension_prev->store(extension_next, std::memory_order_relaxed);
405 
406  // release the bucket lock and increase the version
407  // (14) - this release-store synchronizes-with the acquire-CAS (7, 30, 34, 37) and the acquire-load (23)
408  unlocker.unlock(state.new_version(), std::memory_order_release);
409 
410  free_extension_item(extension);
411  return true;
412  }
413  extension_prev = &extension->next;
414  extension = extension_prev->load(std::memory_order_relaxed);
415  }
416 
417  // key not found
418 
419  // release the bucket lock
420  unlocker.unlock(state, std::memory_order_relaxed);
421 
422  return false;
423 }
424 
425 template <class Key, class Value, class... Policies>
427  if (pos.extension) {
428  // the item we are currently looking at is an extension item
429  auto next = pos.extension->next.load(std::memory_order_relaxed);
430  pos.prev->store(next, std::memory_order_relaxed);
431  auto new_state = pos.current_bucket_state.locked().new_version();
432  // (15) - this release-store synchronizes-with the acquire-load (23)
433  pos.current_bucket->state.store(new_state, std::memory_order_release);
434 
435  free_extension_item(pos.extension);
436  pos.extension = next;
437  if (next == nullptr) {
438  pos.move_to_next_bucket();
439  }
440  return;
441  }
442 
443  // the item we are currently looking at is in the bucket array
444 
445  auto extension = pos.current_bucket->head.load(std::memory_order_relaxed);
446  if (extension) {
447  auto locked_state = pos.current_bucket_state.locked();
448  auto marked_state = locked_state.set_delete_marker(pos.index + 1);
449  pos.current_bucket->state.store(marked_state, std::memory_order_relaxed);
450  assert(pos.current_bucket->state.load().is_locked());
451 
452  auto k = extension->key.load(std::memory_order_relaxed);
453  auto v = extension->value.load(std::memory_order_relaxed);
454  pos.current_bucket->key[pos.index].store(k, std::memory_order_relaxed);
455  // (16) - this release-store synchronizes-with the acquire-load (24)
456  pos.current_bucket->value[pos.index].store(v, std::memory_order_release);
457 
458  // reset the delete marker
459  locked_state = locked_state.new_version();
460  // (17) - this release-store synchronizes-with the acquire-load (23)
461  pos.current_bucket->state.store(locked_state, std::memory_order_release);
462  assert(pos.current_bucket->state.load().is_locked());
463 
464  auto next = extension->next.load(std::memory_order_relaxed);
465  // (18) - this release-store synchronizes-with the acquire-load (25)
466  pos.current_bucket->head.store(next, std::memory_order_release);
467 
468  // increase the version but keep the lock
469  // (19) - this release-store synchronizes-with the acquire-load (23)
470  pos.current_bucket->state.store(locked_state.new_version(), std::memory_order_release);
471  assert(pos.current_bucket->state.load().is_locked());
472  free_extension_item(extension);
473  } else {
474  auto max_index = pos.current_bucket_state.item_count() - 1;
475  if (pos.index != max_index) {
476  // signal which item we are deleting
477  auto locked_state = pos.current_bucket_state.locked();
478  auto marked_state = locked_state.set_delete_marker(pos.index + 1);
479  pos.current_bucket->state.store(marked_state, std::memory_order_relaxed);
480  assert(pos.current_bucket->state.load().is_locked());
481 
482  auto k = pos.current_bucket->key[max_index].load(std::memory_order_relaxed);
483  auto v = pos.current_bucket->value[max_index].load(std::memory_order_relaxed);
484  pos.current_bucket->key[pos.index].store(k, std::memory_order_relaxed);
485  // (20) - this release-store synchronizes-with the acquire-load (24)
486  pos.current_bucket->value[pos.index].store(v, std::memory_order_release);
487  }
488 
489  auto new_state = pos.current_bucket_state.new_version().dec_item_count();
490  pos.current_bucket_state = new_state;
491 
492  // (21) - this release store synchronizes-with the acquire-load (23)
493  pos.current_bucket->state.store(new_state.locked(), std::memory_order_release);
494  assert(pos.current_bucket->state.load().is_locked());
495  if (pos.index == new_state.item_count()) {
496  pos.prev = &pos.current_bucket->head;
497  pos.extension = pos.current_bucket->head.load(std::memory_order_relaxed);
498  if (pos.extension == nullptr) {
499  pos.move_to_next_bucket();
500  }
501  }
502  }
503 }
504 
505 template <class Key, class Value, class... Policies>
506 bool vyukov_hash_map<Key, Value, Policies...>::try_get_value(const key_type& key, accessor& result) const {
507  const hash_t h = hash{}(key);
508 
509  // (22) - this acquire-load synchronizes-with the release-store (31)
510  guarded_block b = acquire_guard(data_block, std::memory_order_acquire);
511  const std::size_t bucket_idx = h & b->mask;
512  bucket& bucket = b->buckets()[bucket_idx];
513 
514 retry:
515  // data_block (and therefore the bucket) can only change due to a concurrent grow()
516  // operation, but since grow() does not change the content of a bucket we can ignore
517  // it and don't have to reread everything during a retry.
518 
519  // (23) - this acquire-load synchronizes-with the release-store (3, 5, 9, 11, 13, 14, 15, 17, 19, 21, 36, 38)
520  bucket_state state = bucket.state.load(std::memory_order_acquire);
521 
522  std::uint32_t item_count = state.item_count();
523  for (std::uint32_t i = 0; i != item_count; ++i) {
524  if (traits::compare_trivial_key(bucket.key[i], key, h)) {
525  // use acquire semantic here - should synchronize-with the release store to value
526  // in remove() to ensure that if we see the changed value here we also see the
527  // changed state in the subsequent reload of state
528  // (24) - this acquire-load synchronizes-with the release-store (8, 12, 16, 20)
529  accessor acc = traits::acquire(bucket.value[i], std::memory_order_acquire);
530 
531  // ensure that we can use the value we just read
532  const auto state2 = bucket.state.load(std::memory_order_relaxed);
533  if (state.version() != state2.version()) {
534  // a deletion has occured in the meantime -> we have to retry
535  state = state2;
536  goto retry;
537  }
538 
539  const auto delete_marker = i + 1;
540  if (state2.delete_marker() == delete_marker) {
541  // Some other thread is currently deleting the entry at this index.
542  // The key we just read can be either the original one (i.e., the key that is
543  // currently beeing deleted), or another key that is beeing moved to this slot
544  // to replace the deleted entry.
545  // Unfortunately we cannot differentiate between these two cases, so we simply
546  // ignore this item and continue with our search. If we can finish our search
547  // before the deleting thread can finish moving some key/value pair into this
548  // slot everything is fine. If the delete operation finished before our search,
549  // we will recognize this by an updated state and retry.
550  continue;
551  }
552 
553  if (!traits::compare_nontrivial_key(acc, key)) {
554  continue;
555  }
556 
557  result = std::move(acc);
558  return true;
559  }
560  }
561 
562  // (25) - this acquire-load synchronizes-with the release-store (4, 10, 18)
563  extension_item* extension = bucket.head.load(std::memory_order_acquire);
564  while (extension) {
565  if (traits::compare_trivial_key(extension->key, key, h)) {
566  // TODO - this acquire does not synchronize with anything ATM.
567  // However, this is probably required when introducing an update-method that
568  // allows to store a new value.
569  // (26) - this acquire-load synchronizes-with <nothing>
570  accessor acc = traits::acquire(extension->value, std::memory_order_acquire);
571 
572  auto state2 = bucket.state.load(std::memory_order_relaxed);
573  if (state.version() != state2.version()) {
574  // a deletion has occured in the meantime -> we have to retry
575  state = state2;
576  goto retry;
577  }
578 
579  if (!traits::compare_nontrivial_key(acc, key)) {
580  continue;
581  }
582 
583  result = std::move(acc);
584  return true;
585  }
586 
587  // (27) - this acquire-load synchronizes-with the release-store (35)
588  extension = extension->next.load(std::memory_order_acquire);
589  auto state2 = bucket.state.load(std::memory_order_relaxed);
590  if (state.version() != state2.version()) {
591  // a deletion has occured in the meantime -> we have to retry
592  state = state2;
593  goto retry;
594  }
595  }
596 
597  auto state2 = bucket.state.load(std::memory_order_relaxed);
598  if (state.version() != state2.version()) {
599  state = state2;
600  // a deletion has occured -> we have to retry since the entry we are looking for might
601  // have been moved while we were searching
602  goto retry;
603  }
604 
605  return false;
606 }
607 
608 template <class Key, class Value, class... Policies>
609 void vyukov_hash_map<Key, Value, Policies...>::grow(bucket& bucket, bucket_state state) {
610  // try to acquire the resizeLock
611  const int already_resizing = resize_lock.exchange(1, std::memory_order_relaxed);
612 
613  // release the bucket lock
614  bucket.state.store(state, std::memory_order_relaxed);
615 
616  // we intentionally release the bucket lock only after we tried to acquire
617  // the resize_lock, to avoid the situation where we might miss a resize
618  // performed by some other thread.
619 
620  if (already_resizing != 0) {
621  backoff backoff;
622  // another thread is already resizing -> wait for it to finish
623  // (28) - this acquire-load synchronizes-with the release-store (32)
624  while (resize_lock.load(std::memory_order_acquire) != 0) {
625  backoff();
626  }
627 
628  return;
629  }
630 
631  do_grow();
632 }
633 
634 template <class Key, class Value, class... Policies>
635 void vyukov_hash_map<Key, Value, Policies...>::do_grow() {
636  // Note: since we hold the resize lock, nobody can replace the current block
637  // (29) - this acquire-load synchronizes-with the release-store (31)
638  auto old_block = data_block.load(std::memory_order_acquire);
639  const auto bucket_count = old_block->bucket_count;
640  block* new_block = allocate_block(bucket_count * 2);
641  if (new_block == nullptr) {
642  resize_lock.store(0, std::memory_order_relaxed);
643  throw std::bad_alloc();
644  }
645 
646  // lock all buckets
647  auto old_buckets = old_block->buckets();
648  for (std::uint32_t i = 0; i != bucket_count; ++i) {
649  auto& bucket = old_buckets[i];
650  backoff backoff;
651  for (;;) {
652  auto st = bucket.state.load(std::memory_order_relaxed);
653  if (st.is_locked()) {
654  continue;
655  }
656 
657  // (30) - this acquire-CAS synchronizes-with the release-store (3, 5, 11, 13, 14, 36, 38)
658  if (bucket.state.compare_exchange_strong(st, st.locked(), std::memory_order_acquire, std::memory_order_relaxed)) {
659  break; // we've got the lock
660  }
661 
662  backoff();
663  }
664  }
665 
666  auto new_buckets = new_block->buckets();
667  for (std::uint32_t bucket_idx = 0; bucket_idx != bucket_count; ++bucket_idx) {
668  auto& old_bucket = old_buckets[bucket_idx];
669  const std::uint32_t item_count = old_bucket.state.load(std::memory_order_relaxed).item_count();
670  for (std::uint32_t i = 0; i != item_count; ++i) {
671  auto k = old_bucket.key[i].load(std::memory_order_relaxed);
672  hash_t h = traits::template rehash<hash>(k);
673  auto& new_bucket = new_buckets[h & new_block->mask];
674  auto new_bucket_state = new_bucket.state.load(std::memory_order_relaxed);
675  auto new_bucket_count = new_bucket_state.item_count();
676  auto v = old_bucket.value[i].load(std::memory_order_relaxed);
677  new_bucket.key[new_bucket_count].store(k, std::memory_order_relaxed);
678  new_bucket.value[new_bucket_count].store(v, std::memory_order_relaxed);
679  new_bucket.state.store(new_bucket_state.inc_item_count(), std::memory_order_relaxed);
680  }
681 
682  // relaxed ordering is fine since we own the bucket lock
683  for (extension_item* extension = old_bucket.head; extension != nullptr;
684  extension = extension->next.load(std::memory_order_relaxed)) {
685  auto k = extension->key.load(std::memory_order_relaxed);
686  hash_t h = traits::template rehash<hash>(k);
687  auto& new_bucket = new_buckets[h & new_block->mask];
688  auto new_bucket_state = new_bucket.state.load(std::memory_order_relaxed);
689  auto new_bucket_count = new_bucket_state.item_count();
690  auto v = extension->value.load(std::memory_order_relaxed);
691  if (new_bucket_count < bucket_item_count) {
692  new_bucket.key[new_bucket_count].store(k, std::memory_order_relaxed);
693  new_bucket.value[new_bucket_count].store(v, std::memory_order_relaxed);
694  new_bucket.state.store(new_bucket_state.inc_item_count(), std::memory_order_relaxed);
695  } else {
696  extension_item* new_extension = allocate_extension_item(new_block, h);
697  assert(new_extension);
698  new_extension->key.store(k, std::memory_order_relaxed);
699  new_extension->value.store(v, std::memory_order_relaxed);
700  new_extension->next.store(new_bucket.head, std::memory_order_relaxed);
701  new_bucket.head = new_extension;
702  }
703  }
704  }
705  // (31) - this release-store synchronizes-with (6, 22, 29, 33)
706  data_block.store(new_block, std::memory_order_release);
707  // (32) - this release-store synchronizes-with the acquire-load (28)
708  resize_lock.store(0, std::memory_order_release);
709 
710  // reclaim the old data block
711  guarded_block g(old_block);
712  g.reclaim();
713 }
714 
715 template <class Key, class Value, class... Policies>
716 auto vyukov_hash_map<Key, Value, Policies...>::allocate_block(std::uint32_t bucket_count) -> block* {
717  std::uint32_t extension_bucket_count = bucket_count / bucket_to_extension_ratio;
718  std::size_t size = sizeof(block) + sizeof(bucket) * bucket_count +
719  sizeof(extension_bucket) * (static_cast<size_t>(extension_bucket_count) + 1);
720 
721  void* mem = ::operator new(size, cacheline_size, std::nothrow);
722  if (mem == nullptr) {
723  return nullptr;
724  }
725 
726  std::memset(mem, 0, size);
727  auto* b = new (mem) block;
728  b->mask = bucket_count - 1;
729  b->bucket_count = bucket_count;
730  b->extension_bucket_count = extension_bucket_count;
731 
732  std::size_t extension_bucket_addr = reinterpret_cast<std::size_t>(b) + sizeof(block) + sizeof(bucket) * bucket_count;
733  if (extension_bucket_addr % sizeof(extension_bucket) != 0) {
734  extension_bucket_addr += sizeof(extension_bucket) - (extension_bucket_addr % sizeof(extension_bucket));
735  }
736  b->extension_buckets = reinterpret_cast<extension_bucket*>(extension_bucket_addr);
737 
738  for (std::uint32_t i = 0; i != extension_bucket_count; ++i) {
739  auto& bucket = b->extension_buckets[i];
740  extension_item* head = nullptr;
741  for (std::size_t j = 0; j != extension_item_count; ++j) {
742  bucket.items[j].next.store(head, std::memory_order_relaxed);
743  head = &bucket.items[j];
744  }
745  bucket.head.store(head, std::memory_order_relaxed);
746  }
747 
748  return b;
749 }
750 
751 template <class Key, class Value, class... Policies>
753  const auto h = hash{}(key);
754  iterator result;
755 
756  result.current_bucket = &lock_bucket(h, result.block, result.current_bucket_state);
757  auto& bucket = *result.current_bucket;
758 
759  accessor acc;
760  auto item_count = result.current_bucket_state.item_count();
761  for (std::uint32_t i = 0; i != item_count; ++i) {
762  if (traits::template compare_key<false>(bucket.key[i], bucket.value[i], key, h, acc)) {
763  result.index = i;
764  return result;
765  }
766  }
767 
768  auto extension = bucket.head.load(std::memory_order_relaxed);
769  while (extension) {
770  if (traits::template compare_key<false>(extension->key, extension->value, key, h, acc)) {
771  result.extension = extension;
772  return result;
773  }
774  extension = extension->next.load(std::memory_order_relaxed);
775  }
776 
777  return end();
778 }
779 
780 template <class Key, class Value, class... Policies>
782  iterator result;
783  result.current_bucket = &lock_bucket(0, result.block, result.current_bucket_state);
784  if (result.current_bucket_state.item_count() == 0) {
785  result.move_to_next_bucket();
786  }
787  return result;
788 }
789 
790 template <class Key, class Value, class... Policies>
791 auto vyukov_hash_map<Key, Value, Policies...>::lock_bucket(hash_t hash, guarded_block& block, bucket_state& state)
792  -> bucket& {
793  backoff backoff;
794  for (;;) {
795  // (33) - this acquire-load synchronizes-with the release-store (31)
796  block.acquire(data_block, std::memory_order_acquire);
797  const std::size_t bucket_idx = hash & block->mask;
798  auto& bucket = block->buckets()[bucket_idx];
799  bucket_state st = bucket.state.load(std::memory_order_relaxed);
800  if (st.is_locked()) {
801  continue;
802  }
803 
804  // (34) - this acquire-CAS synchronizes-with the release-store (3, 5, 11, 13, 14, 36, 38)
805  if (bucket.state.compare_exchange_strong(st, st.locked(), std::memory_order_acquire, std::memory_order_relaxed)) {
806  state = st;
807  return bucket;
808  }
809  backoff();
810  }
811 }
812 
813 template <class Key, class Value, class... Policies>
814 auto vyukov_hash_map<Key, Value, Policies...>::allocate_extension_item(block* b, hash_t hash) -> extension_item* {
815  const std::size_t extension_bucket_count = b->extension_bucket_count;
816  const std::size_t mod_mask = extension_bucket_count - 1;
817  for (std::size_t iter = 0; iter != 2; ++iter) {
818  for (std::size_t idx = 0; idx != extension_bucket_count; ++idx) {
819  const std::size_t extension_bucket_idx = (hash + idx) & mod_mask;
820  extension_bucket& extension_bucket = b->extension_buckets[extension_bucket_idx];
821 
822  if (extension_bucket.head.load(std::memory_order_relaxed) == nullptr) {
823  continue;
824  }
825 
826  extension_bucket.acquire_lock();
827  auto item = extension_bucket.head.load(std::memory_order_relaxed);
828  if (item) {
829  extension_bucket.head.store(item->next, std::memory_order_relaxed);
830  extension_bucket.release_lock();
831  return item;
832  }
833  extension_bucket.release_lock();
834  }
835  }
836  return nullptr;
837 }
838 
839 template <class Key, class Value, class... Policies>
840 void vyukov_hash_map<Key, Value, Policies...>::free_extension_item(extension_item* item) {
841  auto item_addr = reinterpret_cast<std::uintptr_t>(item);
842  auto bucket = reinterpret_cast<extension_bucket*>(item_addr - item_addr % sizeof(extension_bucket));
843 
844  bucket->acquire_lock();
845  auto head = bucket->head.load(std::memory_order_relaxed);
846  // (35) - this release-store synchronizes-with the acquire-load (27)
847  item->next.store(head, std::memory_order_release);
848  // we need to use release semantic here to ensure that threads in try_get_value
849  // that see the value written by this store also see the updated bucket_state.
850  bucket->head.store(item, std::memory_order_relaxed);
851  bucket->release_lock();
852 }
853 
854 template <class Key, class Value, class... Policies>
855 vyukov_hash_map<Key, Value, Policies...>::iterator::iterator(iterator&& other) :
856  block(std::move(other.block)),
857  current_bucket(std::move(other.current_bucket)),
858  current_bucket_state(std::move(other.current_bucket_state)),
859  index(std::move(other.index)),
860  extension(std::move(other.extension)),
861  prev(std::move(other.prev)) {
862  other.block.reset();
863  other.current_bucket = nullptr;
864  other.current_bucket_state = bucket_state();
865  other.index = 0;
866  other.extension = nullptr;
867  other.prev = nullptr;
868 }
869 
870 template <class Key, class Value, class... Policies>
871 auto vyukov_hash_map<Key, Value, Policies...>::iterator::operator=(iterator&& other) -> iterator& {
872  block = std::move(other.block);
873  current_bucket = std::move(other.current_bucket);
874  current_bucket_state = std::move(other.current_bucket_state);
875  index = std::move(other.index);
876  extension = std::move(other.extension);
877  prev = std::move(other.prev);
878 
879  other.block.reset();
880  other.current_bucket = nullptr;
881  other.current_bucket_state = bucket_state();
882  other.index = 0;
883  other.extension = nullptr;
884  other.prev = nullptr;
885 
886  return *this;
887 }
888 
889 template <class Key, class Value, class... Policies>
890 vyukov_hash_map<Key, Value, Policies...>::iterator::~iterator() {
891  reset();
892 }
893 
894 template <class Key, class Value, class... Policies>
896  // unlock the current bucket
897  if (current_bucket) {
898  assert(current_bucket->state.load().is_locked());
899  // (36) - this release-store synchronizes-with the acquire-CAS (7, 30, 34, 37) and the acquire-load (23)
900  current_bucket->state.store(current_bucket_state, std::memory_order_release);
901  }
902 
903  block.reset();
904  current_bucket = nullptr;
905  current_bucket_state = bucket_state();
906  index = 0;
907  extension = nullptr;
908  prev = nullptr;
909 }
910 
911 template <class Key, class Value, class... Policies>
913  return block == r.block && current_bucket == r.current_bucket && current_bucket_state == r.current_bucket_state &&
914  index == r.index && extension == r.extension;
915 }
916 
917 template <class Key, class Value, class... Policies>
919  return !(*this == r);
920 }
921 
922 template <class Key, class Value, class... Policies>
923 auto vyukov_hash_map<Key, Value, Policies...>::iterator::operator++() -> iterator& {
924  if (extension) {
925  prev = &extension->next;
926  extension = extension->next.load(std::memory_order_relaxed);
927  if (extension == nullptr) {
928  move_to_next_bucket();
929  }
930  return *this;
931  }
932 
933  ++index;
934  if (index == current_bucket_state.item_count()) {
935  prev = &current_bucket->head;
936  extension = current_bucket->head.load(std::memory_order_relaxed);
937  if (extension == nullptr) {
938  move_to_next_bucket();
939  }
940  }
941  return *this;
942 }
943 
944 template <class Key, class Value, class... Policies>
945 auto vyukov_hash_map<Key, Value, Policies...>::iterator::operator->() -> pointer {
946  static_assert(std::is_reference<reference>::value,
947  "operator-> is only available for instantiations with non-trivial key types. Use explicit "
948  "dereferenciation instead (operator*). The reason is that all other instantiations create "
949  "temporary std::pair<> instances since key and value are stored separately.");
950  return &this->operator*();
951 }
952 
953 template <class Key, class Value, class... Policies>
954 auto vyukov_hash_map<Key, Value, Policies...>::iterator::operator*() -> reference {
955  if (extension) {
956  return traits::deref_iterator(extension->key, extension->value);
957  }
958  return traits::deref_iterator(current_bucket->key[index], current_bucket->value[index]);
959 }
960 
961 template <class Key, class Value, class... Policies>
962 void vyukov_hash_map<Key, Value, Policies...>::iterator::move_to_next_bucket() {
963  assert(current_bucket != nullptr);
964  if (current_bucket == block->buckets() + block->bucket_count - 1) {
965  // we reached the end of the container -> reset the iterator
966  reset();
967  return;
968  }
969 
970  auto old_bucket = current_bucket;
971  auto old_bucket_state = current_bucket_state;
972  ++current_bucket; // move pointer to the next bucket
973 
974  // TODO - no need to lock if bucket is empty
975 
976  backoff backoff;
977  for (;;) {
978  auto st = current_bucket->state.load(std::memory_order_relaxed);
979  if (st.is_locked()) {
980  continue;
981  }
982 
983  // (37) - this acquire-CAS synchronizes-with the release-store (3, 5, 11, 13, 14, 36, 38)
984  if (current_bucket->state.compare_exchange_strong(
985  st, st.locked(), std::memory_order_acquire, std::memory_order_relaxed)) {
986  current_bucket_state = st;
987  break;
988  }
989  backoff();
990  }
991 
992  // (38) - this release-store synchronizes-with the acquire-CAS (7, 30, 34, 37) and the acquire-load (23)
993  old_bucket->state.store(old_bucket_state, std::memory_order_release); // unlock the previous bucket
994 
995  index = 0;
996  extension = nullptr;
997  prev = nullptr;
998  if (current_bucket_state.item_count() == 0) {
999  move_to_next_bucket();
1000  }
1001 }
1002 
1003 } // namespace xenium
1004 
1005 #ifdef _MSC_VER
1006  #pragma warning(pop)
1007 #endif
xenium::vyukov_hash_map::iterator
A ForwardIterator to safely iterate vyukov_hash_map.
Definition: vyukov_hash_map.hpp:367
xenium::vyukov_hash_map::emplace
bool emplace(key_type key, value_type value)
Inserts a new element into the container if the container doesn't already contain an element with an ...
Definition: vyukov_hash_map.hpp:206
xenium::vyukov_hash_map::find
iterator find(const key_type &key)
Finds an element with key equivalent to key.
Definition: vyukov_hash_map.hpp:752
xenium::vyukov_hash_map
A concurrent hash-map that uses fine-grained locking.
Definition: vyukov_hash_map.hpp:135
xenium::vyukov_hash_map::get_or_emplace_lazy
std::pair< accessor, bool > get_or_emplace_lazy(key_type key, Factory &&factory)
Inserts a new element into the container if the container doesn't already contain an element with an ...
xenium::hash
Slim wrapper around std::hash with specialization for pointer types.
Definition: hash.hpp:25
xenium::vyukov_hash_map::extract
bool extract(const key_type &key, accessor &accessor)
Removes the element with the key equivalent to key (if one exists), and provides an accessor to the r...
Definition: vyukov_hash_map.hpp:311
xenium::vyukov_hash_map::erase
bool erase(const key_type &key)
Removes the element with the key equivalent to key (if one exists).
Definition: vyukov_hash_map.hpp:303
xenium::vyukov_hash_map::get_or_emplace
std::pair< accessor, bool > get_or_emplace(key_type key, Args &&... args)
Inserts a new element into the container if the container doesn't already contain an element with an ...
xenium::vyukov_hash_map::try_get_value
bool try_get_value(const key_type &key, accessor &result) const
Provides an accessor to the value associated with the specified key, if such an element exists in the...
Definition: vyukov_hash_map.hpp:506
xenium::vyukov_hash_map::iterator::reset
void reset()
Releases the bucket lock and resets the iterator.
Definition: vyukov_hash_map.hpp:895
xenium::vyukov_hash_map::begin
iterator begin()
Returns an iterator to the first element of the container.
Definition: vyukov_hash_map.hpp:781