Blender V4.3
mikk_atomic_hash_set.hh
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2012-2021 Meta Platforms, Inc. and affiliates.
2 * SPDX-FileCopyrightText: 2022 Blender Authors
3 *
4 * SPDX-License-Identifier: Apache-2.0 */
5
6/* Simplified version of Folly's AtomicHashArray
7 * (https://github.com/facebook/folly/blob/main/folly/AtomicHashArray.h).
8 *
9 * Notable changes:
10 * - Standalone and header-only.
11 * - Behaves like a set, not like a map: There's no value type anymore, only keys.
12 * - Capacity check logic have been removed, the code assumes you know the required size in
13 * advance.
14 * - Custom allocator support has been removed.
15 * - Erase has been removed.
16 * - Find has been removed.
17 */
18
23#pragma once
24
25#ifdef _MSC_VER
26# include <intrin.h>
27#endif
28
29#include <atomic>
30#include <type_traits>
31
32namespace mikk {
33
35 inline size_t operator()(size_t idx, size_t /* numProbes */, size_t capacity) const
36 {
37 idx += 1; // linear probing
38
39 // Avoid modulus because it's slow
40 return LIKELY(idx < capacity) ? idx : (idx - capacity);
41 }
42};
43
45 inline size_t operator()(size_t idx, size_t numProbes, size_t capacity) const
46 {
47 idx += numProbes; // quadratic probing
48
49 // Avoid modulus because it's slow
50 return LIKELY(idx < capacity) ? idx : (idx - capacity);
51 }
52};
53
54template<class KeyT,
55 bool isAtomic,
56 class KeyHash = std::hash<KeyT>,
57 class KeyEqual = std::equal_to<KeyT>,
58 class ProbeFcn = AtomicHashSetLinearProbeFcn>
60 static_assert((std::is_convertible<KeyT, int32_t>::value ||
61 std::is_convertible<KeyT, int64_t>::value ||
62 std::is_convertible<KeyT, const void *>::value),
63 "You are trying to use AtomicHashSet with disallowed key "
64 "types. You must use atomically compare-and-swappable integer "
65 "keys, or a different container class.");
66
67 public:
68 const size_t capacity_;
69 const KeyT kEmptyKey_;
70
71 KeyHash hasher_;
73
74 private:
75 size_t kAnchorMask_;
76 /* When using a single thread, we can avoid overhead by not bothering with atomic cells. */
77 typedef typename std::conditional<isAtomic, std::atomic<KeyT>, KeyT>::type cell_type;
78 std::vector<cell_type> cells_;
79
80 public:
81 struct Config {
85 size_t capacity; // if positive, overrides maxLoadFactor
86
87 // Cannot have constexpr ctor because some compilers rightly complain.
88 Config() : emptyKey((KeyT)-1), maxLoadFactor(0.8), growthFactor(-1), capacity(0) {}
89 };
90
91 /* Instead of a mess of arguments, we take a max size and a Config struct to
92 * simulate named ctor parameters. The Config struct has sensible defaults
93 * for everything, but is overloaded - if you specify a positive capacity,
94 * that will be used directly instead of computing it based on maxLoadFactor.
95 */
96 AtomicHashSet(size_t maxSize,
97 KeyHash hasher = KeyHash(),
98 KeyEqual equalityChecker = KeyEqual(),
99 const Config &c = Config())
100 : capacity_(size_t(double(maxSize) / c.maxLoadFactor) + 1),
101 kEmptyKey_(c.emptyKey),
102 hasher_(hasher),
103 equalityChecker_(equalityChecker),
104 cells_(capacity_)
105 {
106 /* Get next power of two. Could be done more effiently with builtin_clz, but this is not
107 * performance-critical. */
108 kAnchorMask_ = 1;
109 while (kAnchorMask_ < capacity_) {
110 kAnchorMask_ *= 2;
111 }
112 /* Get mask for lower bits. */
113 kAnchorMask_ -= 1;
114
115 /* Not great, but the best we can do to support both atomic and non-atomic cells
116 * since std::atomic doesn't have a copy constructor so cells_(capacity_, kEmptyKey_)
117 * in the initializer list won't work. */
118 std::fill((KeyT *)cells_.data(), (KeyT *)cells_.data() + capacity_, kEmptyKey_);
119 }
120
121 AtomicHashSet(const AtomicHashSet &) = delete;
123
124 ~AtomicHashSet() = default;
125
126 /* Sequential specialization. */
127 bool tryUpdateCell(KeyT *cell, KeyT &existingKey, KeyT newKey)
128 {
129 if (*cell == existingKey) {
130 *cell = newKey;
131 return true;
132 }
133 existingKey = *cell;
134 return false;
135 }
136
137 /* Atomic specialization. */
138 bool tryUpdateCell(std::atomic<KeyT> *cell, KeyT &existingKey, KeyT newKey)
139 {
140 return cell->compare_exchange_strong(existingKey, newKey, std::memory_order_acq_rel);
141 }
142
143 std::pair<KeyT, bool> emplace(KeyT key)
144 {
145 size_t idx = keyToAnchorIdx(key);
146 size_t numProbes = 0;
147 for (;;) {
148 cell_type *cell = &cells_[idx];
149 KeyT existingKey = kEmptyKey_;
150 /* Try to replace empty cell with our key. */
151 if (tryUpdateCell(cell, existingKey, key)) {
152 /* Cell was empty, we're done. */
153 return std::make_pair(key, true);
154 }
155
156 /* Cell was not empty, check if the existing key is equal. */
157 if (equalityChecker_(existingKey, key)) {
158 /* Found equal element, we're done. */
159 return std::make_pair(existingKey, false);
160 }
161
162 /* Continue to next cell according to probe strategy. */
163 ++numProbes;
164 if (UNLIKELY(numProbes >= capacity_)) {
165 // probed every cell...fail
166 assert(false);
167 return std::make_pair(kEmptyKey_, false);
168 }
169
170 idx = ProbeFcn()(idx, numProbes, capacity_);
171 }
172 }
173
174 private:
175 inline size_t keyToAnchorIdx(const KeyT k) const
176 {
177 const size_t hashVal = hasher_(k);
178 const size_t probe = hashVal & kAnchorMask_;
179 return LIKELY(probe < capacity_) ? probe : hashVal % capacity_;
180 }
181
182}; // AtomicHashSet
183
184} // namespace mikk
#define UNLIKELY(x)
#define LIKELY(x)
typedef double(DMatrix)[4][4]
AtomicHashSet & operator=(const AtomicHashSet &)=delete
AtomicHashSet(size_t maxSize, KeyHash hasher=KeyHash(), KeyEqual equalityChecker=KeyEqual(), const Config &c=Config())
bool tryUpdateCell(std::atomic< KeyT > *cell, KeyT &existingKey, KeyT newKey)
bool tryUpdateCell(KeyT *cell, KeyT &existingKey, KeyT newKey)
std::pair< KeyT, bool > emplace(KeyT key)
~AtomicHashSet()=default
AtomicHashSet(const AtomicHashSet &)=delete
size_t operator()(size_t idx, size_t, size_t capacity) const
size_t operator()(size_t idx, size_t numProbes, size_t capacity) const