2 * Copyright 2015 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #ifndef FOLLY_ATOMICHASHARRAY_H_
18 #error "This should only be included by AtomicHashArray.h"
21 #include <folly/Bits.h>
22 #include <folly/detail/AtomicHashUtils.h>
26 template <class KeyT, class ValueT,
27 class HashFcn, class EqualFcn, class Allocator>
28 const KeyT AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::Config::
30 template <class KeyT, class ValueT,
31 class HashFcn, class EqualFcn, class Allocator>
32 const KeyT AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::Config::
33 kLockedKey = (KeyT)-2;
34 template <class KeyT, class ValueT,
35 class HashFcn, class EqualFcn, class Allocator>
36 const KeyT AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::Config::
37 kErasedKey = (KeyT)-3;
39 // AtomicHashArray private constructor --
40 template <class KeyT, class ValueT,
41 class HashFcn, class EqualFcn, class Allocator>
42 AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::
43 AtomicHashArray(size_t capacity, KeyT emptyKey, KeyT lockedKey,
44 KeyT erasedKey, double _maxLoadFactor, size_t cacheSize)
45 : capacity_(capacity),
46 maxEntries_(size_t(_maxLoadFactor * capacity_ + 0.5)),
47 kEmptyKey_(emptyKey), kLockedKey_(lockedKey), kErasedKey_(erasedKey),
48 kAnchorMask_(nextPowTwo(capacity_) - 1), numEntries_(0, cacheSize),
49 numPendingEntries_(0, cacheSize), isFull_(0), numErases_(0) {
55 * Sets ret.second to value found and ret.index to index
56 * of key and returns true, or if key does not exist returns false and
57 * ret.index is set to capacity_.
59 template <class KeyT, class ValueT,
60 class HashFcn, class EqualFcn, class Allocator>
61 typename AtomicHashArray<KeyT, ValueT,
62 HashFcn, EqualFcn, Allocator>::SimpleRetT
63 AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::
64 findInternal(const KeyT key_in) {
65 DCHECK_NE(key_in, kEmptyKey_);
66 DCHECK_NE(key_in, kLockedKey_);
67 DCHECK_NE(key_in, kErasedKey_);
68 for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;
70 idx = probeNext(idx, numProbes)) {
71 const KeyT key = acquireLoadKey(cells_[idx]);
72 if (LIKELY(EqualFcn()(key, key_in))) {
73 return SimpleRetT(idx, true);
75 if (UNLIKELY(key == kEmptyKey_)) {
76 // if we hit an empty element, this key does not exist
77 return SimpleRetT(capacity_, false);
80 if (UNLIKELY(numProbes >= capacity_)) {
81 // probed every cell...fail
82 return SimpleRetT(capacity_, false);
90 * Returns false on failure due to key collision or full.
91 * Also sets ret.index to the index of the key. If the map is full, sets
92 * ret.index = capacity_. Also sets ret.second to cell value, thus if insert
93 * successful this will be what we just inserted, if there is a key collision
94 * this will be the previously inserted value, and if the map is full it is
97 template <class KeyT, class ValueT,
98 class HashFcn, class EqualFcn, class Allocator>
100 typename AtomicHashArray<KeyT, ValueT,
101 HashFcn, EqualFcn, Allocator>::SimpleRetT
102 AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::
103 insertInternal(KeyT key_in, T&& value) {
104 const short NO_NEW_INSERTS = 1;
105 const short NO_PENDING_INSERTS = 2;
106 CHECK_NE(key_in, kEmptyKey_);
107 CHECK_NE(key_in, kLockedKey_);
108 CHECK_NE(key_in, kErasedKey_);
110 size_t idx = keyToAnchorIdx(key_in);
111 size_t numProbes = 0;
113 DCHECK_LT(idx, capacity_);
114 value_type* cell = &cells_[idx];
115 if (relaxedLoadKey(*cell) == kEmptyKey_) {
116 // NOTE: isFull_ is set based on numEntries_.readFast(), so it's
117 // possible to insert more than maxEntries_ entries. However, it's not
118 // possible to insert past capacity_.
119 ++numPendingEntries_;
120 if (isFull_.load(std::memory_order_acquire)) {
121 --numPendingEntries_;
123 // Before deciding whether this insert succeeded, this thread needs to
124 // wait until no other thread can add a new entry.
126 // Correctness assumes isFull_ is true at this point. If
127 // another thread now does ++numPendingEntries_, we expect it
128 // to pass the isFull_.load() test above. (It shouldn't insert
130 detail::atomic_hash_spin_wait([&] {
132 (isFull_.load(std::memory_order_acquire) != NO_PENDING_INSERTS) &&
133 (numPendingEntries_.readFull() != 0);
135 isFull_.store(NO_PENDING_INSERTS, std::memory_order_release);
137 if (relaxedLoadKey(*cell) == kEmptyKey_) {
138 // Don't insert past max load factor
139 return SimpleRetT(capacity_, false);
142 // An unallocated cell. Try once to lock it. If we succeed, insert here.
143 // If we fail, fall through to comparison below; maybe the insert that
144 // just beat us was for this very key....
145 if (tryLockCell(cell)) {
146 // Write the value - done before unlocking
148 DCHECK(relaxedLoadKey(*cell) == kLockedKey_);
150 * This happens using the copy constructor because we won't have
151 * constructed a lhs to use an assignment operator on when
152 * values are being set.
154 new (&cell->second) ValueT(std::forward<T>(value));
155 unlockCell(cell, key_in); // Sets the new key
157 // Transition back to empty key---requires handling
158 // locked->empty below.
159 unlockCell(cell, kEmptyKey_);
160 --numPendingEntries_;
163 // Direct comparison rather than EqualFcn ok here
164 // (we just inserted it)
165 DCHECK(relaxedLoadKey(*cell) == key_in);
166 --numPendingEntries_;
167 ++numEntries_; // This is a thread cached atomic increment :)
168 if (numEntries_.readFast() >= maxEntries_) {
169 isFull_.store(NO_NEW_INSERTS, std::memory_order_relaxed);
171 return SimpleRetT(idx, true);
173 --numPendingEntries_;
176 DCHECK(relaxedLoadKey(*cell) != kEmptyKey_);
177 if (kLockedKey_ == acquireLoadKey(*cell)) {
178 detail::atomic_hash_spin_wait([&] {
179 return kLockedKey_ == acquireLoadKey(*cell);
183 const KeyT thisKey = acquireLoadKey(*cell);
184 if (EqualFcn()(thisKey, key_in)) {
185 // Found an existing entry for our key, but we don't overwrite the
187 return SimpleRetT(idx, false);
188 } else if (thisKey == kEmptyKey_ || thisKey == kLockedKey_) {
189 // We need to try again (i.e., don't increment numProbes or
190 // advance idx): this case can happen if the constructor for
191 // ValueT threw for this very cell (the rethrow block above).
196 if (UNLIKELY(numProbes >= capacity_)) {
197 // probed every cell...fail
198 return SimpleRetT(capacity_, false);
201 idx = probeNext(idx, numProbes);
209 * This will attempt to erase the given key key_in if the key is found. It
210 * returns 1 iff the key was located and marked as erased, and 0 otherwise.
212 * Memory is not freed or reclaimed by erase, i.e. the cell containing the
213 * erased key will never be reused. If there's an associated value, we won't
216 template <class KeyT, class ValueT,
217 class HashFcn, class EqualFcn, class Allocator>
218 size_t AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::
220 CHECK_NE(key_in, kEmptyKey_);
221 CHECK_NE(key_in, kLockedKey_);
222 CHECK_NE(key_in, kErasedKey_);
223 for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;
225 idx = probeNext(idx, numProbes)) {
226 DCHECK_LT(idx, capacity_);
227 value_type* cell = &cells_[idx];
228 KeyT currentKey = acquireLoadKey(*cell);
229 if (currentKey == kEmptyKey_ || currentKey == kLockedKey_) {
230 // If we hit an empty (or locked) element, this key does not exist. This
231 // is similar to how it's handled in find().
234 if (EqualFcn()(currentKey, key_in)) {
235 // Found an existing entry for our key, attempt to mark it erased.
236 // Some other thread may have erased our key, but this is ok.
237 KeyT expect = currentKey;
238 if (cellKeyPtr(*cell)->compare_exchange_strong(expect, kErasedKey_)) {
239 numErases_.fetch_add(1, std::memory_order_relaxed);
241 // Even if there's a value in the cell, we won't delete (or even
242 // default construct) it because some other thread may be accessing it.
243 // Locking it meanwhile won't work either since another thread may be
244 // holding a pointer to it.
246 // We found the key and successfully erased it.
249 // If another thread succeeds in erasing our key, we'll stop our search.
253 if (UNLIKELY(numProbes >= capacity_)) {
254 // probed every cell...fail
260 template <class KeyT, class ValueT,
261 class HashFcn, class EqualFcn, class Allocator>
262 const typename AtomicHashArray<KeyT, ValueT,
263 HashFcn, EqualFcn, Allocator>::Config
264 AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::defaultConfig;
266 template <class KeyT, class ValueT,
267 class HashFcn, class EqualFcn, class Allocator>
268 typename AtomicHashArray<KeyT, ValueT,
269 HashFcn, EqualFcn, Allocator>::SmartPtr
270 AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::
271 create(size_t maxSize, const Config& c) {
272 CHECK_LE(c.maxLoadFactor, 1.0);
273 CHECK_GT(c.maxLoadFactor, 0.0);
274 CHECK_NE(c.emptyKey, c.lockedKey);
275 size_t capacity = size_t(maxSize / c.maxLoadFactor);
276 size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * capacity;
278 auto const mem = Allocator().allocate(sz);
280 new (mem) AtomicHashArray(capacity, c.emptyKey, c.lockedKey, c.erasedKey,
281 c.maxLoadFactor, c.entryCountThreadCacheSize);
283 Allocator().deallocate(mem, sz);
287 SmartPtr map(static_cast<AtomicHashArray*>((void *)mem));
290 * Mark all cells as empty.
292 * Note: we're bending the rules a little here accessing the key
293 * element in our cells even though the cell object has not been
294 * constructed, and casting them to atomic objects (see cellKeyPtr).
295 * (Also, in fact we never actually invoke the value_type
296 * constructor.) This is in order to avoid needing to default
297 * construct a bunch of value_type when we first start up: if you
298 * have an expensive default constructor for the value type this can
299 * noticeably speed construction time for an AHA.
301 FOR_EACH_RANGE(i, 0, map->capacity_) {
302 cellKeyPtr(map->cells_[i])->store(map->kEmptyKey_,
303 std::memory_order_relaxed);
308 template <class KeyT, class ValueT,
309 class HashFcn, class EqualFcn, class Allocator>
310 void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::
311 destroy(AtomicHashArray* p) {
314 size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * p->capacity_;
316 FOR_EACH_RANGE(i, 0, p->capacity_) {
317 if (p->cells_[i].first != p->kEmptyKey_) {
318 p->cells_[i].~value_type();
321 p->~AtomicHashArray();
323 Allocator().deallocate((char *)p, sz);
326 // clear -- clears all keys and values in the map and resets all counters
327 template <class KeyT, class ValueT,
328 class HashFcn, class EqualFcn, class Allocator>
329 void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::
331 FOR_EACH_RANGE(i, 0, capacity_) {
332 if (cells_[i].first != kEmptyKey_) {
333 cells_[i].~value_type();
334 *const_cast<KeyT*>(&cells_[i].first) = kEmptyKey_;
336 CHECK(cells_[i].first == kEmptyKey_);
339 numPendingEntries_.set(0);
340 isFull_.store(0, std::memory_order_relaxed);
341 numErases_.store(0, std::memory_order_relaxed);
345 // Iterator implementation
347 template <class KeyT, class ValueT,
348 class HashFcn, class EqualFcn, class Allocator>
349 template <class ContT, class IterVal>
350 struct AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator>::aha_iterator
351 : boost::iterator_facade<aha_iterator<ContT,IterVal>,
353 boost::forward_traversal_tag>
355 explicit aha_iterator() : aha_(0) {}
357 // Conversion ctor for interoperability between const_iterator and
358 // iterator. The enable_if<> magic keeps us well-behaved for
359 // is_convertible<> (v. the iterator_facade documentation).
360 template<class OtherContT, class OtherVal>
361 aha_iterator(const aha_iterator<OtherContT,OtherVal>& o,
362 typename std::enable_if<
363 std::is_convertible<OtherVal*,IterVal*>::value >::type* = 0)
368 explicit aha_iterator(ContT* array, size_t offset)
373 // Returns unique index that can be used with findAt().
374 // WARNING: The following function will fail silently for hashtable
375 // with capacity > 2^32
376 uint32_t getIndex() const { return offset_; }
378 void advancePastEmpty() {
379 while (offset_ < aha_->capacity_ && !isValid()) {
385 friend class AtomicHashArray;
386 friend class boost::iterator_core_access;
393 bool equal(const aha_iterator& o) const {
394 return aha_ == o.aha_ && offset_ == o.offset_;
397 IterVal& dereference() const {
398 return aha_->cells_[offset_];
401 bool isValid() const {
402 KeyT key = acquireLoadKey(aha_->cells_[offset_]);
403 return key != aha_->kEmptyKey_ &&
404 key != aha_->kLockedKey_ &&
405 key != aha_->kErasedKey_;