folly copyright 2015 -> copyright 2016
[folly.git] / folly / AtomicHashArray-inl.h
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
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
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #ifndef FOLLY_ATOMICHASHARRAY_H_
18 #error "This should only be included by AtomicHashArray.h"
19 #endif
20
21 #include <type_traits>
22
23 #include <folly/Bits.h>
24 #include <folly/detail/AtomicHashUtils.h>
25
26 namespace folly {
27
28 // AtomicHashArray private constructor --
29 template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
30           class Allocator, class ProbeFcn, class KeyConvertFcn>
31 AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
32                 Allocator, ProbeFcn, KeyConvertFcn>::
33 AtomicHashArray(size_t capacity, KeyT emptyKey, KeyT lockedKey,
34                 KeyT erasedKey, double _maxLoadFactor, size_t cacheSize)
35     : capacity_(capacity),
36       maxEntries_(size_t(_maxLoadFactor * capacity_ + 0.5)),
37       kEmptyKey_(emptyKey), kLockedKey_(lockedKey), kErasedKey_(erasedKey),
38       kAnchorMask_(nextPowTwo(capacity_) - 1), numEntries_(0, cacheSize),
39       numPendingEntries_(0, cacheSize), isFull_(0), numErases_(0) {
40 }
41
42 /*
43  * findInternal --
44  *
45  *   Sets ret.second to value found and ret.index to index
46  *   of key and returns true, or if key does not exist returns false and
47  *   ret.index is set to capacity_.
48  */
49 template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
50           class Allocator, class ProbeFcn, class KeyConvertFcn>
51 template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
52 typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
53                          Allocator, ProbeFcn, KeyConvertFcn>::SimpleRetT
54 AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
55                 Allocator, ProbeFcn, KeyConvertFcn>::
56 findInternal(const LookupKeyT key_in) {
57   checkLegalKeyIfKey<LookupKeyT>(key_in);
58
59   for (size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in),
60        numProbes = 0;
61        ;
62        idx = ProbeFcn()(idx, numProbes, capacity_)) {
63     const KeyT key = acquireLoadKey(cells_[idx]);
64     if (LIKELY(LookupEqualFcn()(key, key_in))) {
65       return SimpleRetT(idx, true);
66     }
67     if (UNLIKELY(key == kEmptyKey_)) {
68       // if we hit an empty element, this key does not exist
69       return SimpleRetT(capacity_, false);
70     }
71     // NOTE: the way we count numProbes must be same in find(), insert(),
72     // and erase(). Otherwise it may break probing.
73     ++numProbes;
74     if (UNLIKELY(numProbes >= capacity_)) {
75       // probed every cell...fail
76       return SimpleRetT(capacity_, false);
77     }
78   }
79 }
80
81 /*
82  * insertInternal --
83  *
84  *   Returns false on failure due to key collision or full.
85  *   Also sets ret.index to the index of the key.  If the map is full, sets
86  *   ret.index = capacity_.  Also sets ret.second to cell value, thus if insert
87  *   successful this will be what we just inserted, if there is a key collision
88  *   this will be the previously inserted value, and if the map is full it is
89  *   default.
90  */
91 template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
92           class Allocator, class ProbeFcn, class KeyConvertFcn>
93 template <typename LookupKeyT,
94           typename LookupHashFcn,
95           typename LookupEqualFcn,
96           typename LookupKeyToKeyFcn,
97           typename... ArgTs>
98 typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
99                          Allocator, ProbeFcn, KeyConvertFcn>::SimpleRetT
100 AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
101                 Allocator, ProbeFcn, KeyConvertFcn>::
102 insertInternal(LookupKeyT key_in, ArgTs&&... vCtorArgs) {
103   const short NO_NEW_INSERTS = 1;
104   const short NO_PENDING_INSERTS = 2;
105   checkLegalKeyIfKey<LookupKeyT>(key_in);
106
107   size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in);
108   size_t numProbes = 0;
109   for (;;) {
110     DCHECK_LT(idx, capacity_);
111     value_type* cell = &cells_[idx];
112     if (relaxedLoadKey(*cell) == kEmptyKey_) {
113       // NOTE: isFull_ is set based on numEntries_.readFast(), so it's
114       // possible to insert more than maxEntries_ entries. However, it's not
115       // possible to insert past capacity_.
116       ++numPendingEntries_;
117       if (isFull_.load(std::memory_order_acquire)) {
118         --numPendingEntries_;
119
120         // Before deciding whether this insert succeeded, this thread needs to
121         // wait until no other thread can add a new entry.
122
123         // Correctness assumes isFull_ is true at this point. If
124         // another thread now does ++numPendingEntries_, we expect it
125         // to pass the isFull_.load() test above. (It shouldn't insert
126         // a new entry.)
127         detail::atomic_hash_spin_wait([&] {
128           return
129             (isFull_.load(std::memory_order_acquire) != NO_PENDING_INSERTS) &&
130             (numPendingEntries_.readFull() != 0);
131         });
132         isFull_.store(NO_PENDING_INSERTS, std::memory_order_release);
133
134         if (relaxedLoadKey(*cell) == kEmptyKey_) {
135           // Don't insert past max load factor
136           return SimpleRetT(capacity_, false);
137         }
138       } else {
139         // An unallocated cell. Try once to lock it. If we succeed, insert here.
140         // If we fail, fall through to comparison below; maybe the insert that
141         // just beat us was for this very key....
142         if (tryLockCell(cell)) {
143           KeyT key_new;
144           // Write the value - done before unlocking
145           try {
146             key_new = LookupKeyToKeyFcn()(key_in);
147             typedef typename std::remove_const<LookupKeyT>::type
148               LookupKeyTNoConst;
149             constexpr bool kAlreadyChecked =
150               std::is_same<KeyT, LookupKeyTNoConst>::value;
151             if (!kAlreadyChecked) {
152               checkLegalKeyIfKey(key_new);
153             }
154             DCHECK(relaxedLoadKey(*cell) == kLockedKey_);
155             new (&cell->second) ValueT(std::forward<ArgTs>(vCtorArgs)...);
156             unlockCell(cell, key_new); // Sets the new key
157           } catch (...) {
158             // Transition back to empty key---requires handling
159             // locked->empty below.
160             unlockCell(cell, kEmptyKey_);
161             --numPendingEntries_;
162             throw;
163           }
164           // An erase() can race here and delete right after our insertion
165           // Direct comparison rather than EqualFcn ok here
166           // (we just inserted it)
167           DCHECK(relaxedLoadKey(*cell) == key_new ||
168                  relaxedLoadKey(*cell) == kErasedKey_);
169           --numPendingEntries_;
170           ++numEntries_;  // This is a thread cached atomic increment :)
171           if (numEntries_.readFast() >= maxEntries_) {
172             isFull_.store(NO_NEW_INSERTS, std::memory_order_relaxed);
173           }
174           return SimpleRetT(idx, true);
175         }
176         --numPendingEntries_;
177       }
178     }
179     DCHECK(relaxedLoadKey(*cell) != kEmptyKey_);
180     if (kLockedKey_ == acquireLoadKey(*cell)) {
181       detail::atomic_hash_spin_wait([&] {
182         return kLockedKey_ == acquireLoadKey(*cell);
183       });
184     }
185
186     const KeyT thisKey = acquireLoadKey(*cell);
187     if (LookupEqualFcn()(thisKey, key_in)) {
188       // Found an existing entry for our key, but we don't overwrite the
189       // previous value.
190       return SimpleRetT(idx, false);
191     } else if (thisKey == kEmptyKey_ || thisKey == kLockedKey_) {
192       // We need to try again (i.e., don't increment numProbes or
193       // advance idx): this case can happen if the constructor for
194       // ValueT threw for this very cell (the rethrow block above).
195       continue;
196     }
197
198
199     // NOTE: the way we count numProbes must be same in find(),
200     // insert(), and erase(). Otherwise it may break probing.
201     ++numProbes;
202     if (UNLIKELY(numProbes >= capacity_)) {
203       // probed every cell...fail
204       return SimpleRetT(capacity_, false);
205     }
206
207     idx = ProbeFcn()(idx, numProbes, capacity_);
208   }
209 }
210
211 /*
212  * erase --
213  *
214  *   This will attempt to erase the given key key_in if the key is found. It
215  *   returns 1 iff the key was located and marked as erased, and 0 otherwise.
216  *
217  *   Memory is not freed or reclaimed by erase, i.e. the cell containing the
218  *   erased key will never be reused. If there's an associated value, we won't
219  *   touch it either.
220  */
221 template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
222           class Allocator, class ProbeFcn, class KeyConvertFcn>
223 size_t AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
224                        Allocator, ProbeFcn, KeyConvertFcn>::
225 erase(KeyT key_in) {
226   CHECK_NE(key_in, kEmptyKey_);
227   CHECK_NE(key_in, kLockedKey_);
228   CHECK_NE(key_in, kErasedKey_);
229
230   for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;
231        ;
232        idx = ProbeFcn()(idx, numProbes, capacity_)) {
233     DCHECK_LT(idx, capacity_);
234     value_type* cell = &cells_[idx];
235     KeyT currentKey = acquireLoadKey(*cell);
236     if (currentKey == kEmptyKey_ || currentKey == kLockedKey_) {
237       // If we hit an empty (or locked) element, this key does not exist. This
238       // is similar to how it's handled in find().
239       return 0;
240     }
241     if (EqualFcn()(currentKey, key_in)) {
242       // Found an existing entry for our key, attempt to mark it erased.
243       // Some other thread may have erased our key, but this is ok.
244       KeyT expect = currentKey;
245       if (cellKeyPtr(*cell)->compare_exchange_strong(expect, kErasedKey_)) {
246         numErases_.fetch_add(1, std::memory_order_relaxed);
247
248         // Even if there's a value in the cell, we won't delete (or even
249         // default construct) it because some other thread may be accessing it.
250         // Locking it meanwhile won't work either since another thread may be
251         // holding a pointer to it.
252
253         // We found the key and successfully erased it.
254         return 1;
255       }
256       // If another thread succeeds in erasing our key, we'll stop our search.
257       return 0;
258     }
259
260     // NOTE: the way we count numProbes must be same in find(), insert(),
261     // and erase(). Otherwise it may break probing.
262     ++numProbes;
263     if (UNLIKELY(numProbes >= capacity_)) {
264       // probed every cell...fail
265       return 0;
266     }
267   }
268 }
269
270 template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
271          class Allocator, class ProbeFcn, class KeyConvertFcn>
272 typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
273                          Allocator, ProbeFcn, KeyConvertFcn>::SmartPtr
274 AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
275                 Allocator, ProbeFcn, KeyConvertFcn>::
276 create(size_t maxSize, const Config& c) {
277   CHECK_LE(c.maxLoadFactor, 1.0);
278   CHECK_GT(c.maxLoadFactor, 0.0);
279   CHECK_NE(c.emptyKey, c.lockedKey);
280   size_t capacity = size_t(maxSize / c.maxLoadFactor);
281   size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * capacity;
282
283   auto const mem = Allocator().allocate(sz);
284   try {
285     new (mem) AtomicHashArray(capacity, c.emptyKey, c.lockedKey, c.erasedKey,
286                               c.maxLoadFactor, c.entryCountThreadCacheSize);
287   } catch (...) {
288     Allocator().deallocate(mem, sz);
289     throw;
290   }
291
292   SmartPtr map(static_cast<AtomicHashArray*>((void *)mem));
293
294   /*
295    * Mark all cells as empty.
296    *
297    * Note: we're bending the rules a little here accessing the key
298    * element in our cells even though the cell object has not been
299    * constructed, and casting them to atomic objects (see cellKeyPtr).
300    * (Also, in fact we never actually invoke the value_type
301    * constructor.)  This is in order to avoid needing to default
302    * construct a bunch of value_type when we first start up: if you
303    * have an expensive default constructor for the value type this can
304    * noticeably speed construction time for an AHA.
305    */
306   FOR_EACH_RANGE(i, 0, map->capacity_) {
307     cellKeyPtr(map->cells_[i])->store(map->kEmptyKey_,
308       std::memory_order_relaxed);
309   }
310   return map;
311 }
312
313 template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
314           class Allocator, class ProbeFcn, class KeyConvertFcn>
315 void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
316                      Allocator, ProbeFcn, KeyConvertFcn>::
317 destroy(AtomicHashArray* p) {
318   assert(p);
319
320   size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * p->capacity_;
321
322   FOR_EACH_RANGE(i, 0, p->capacity_) {
323     if (p->cells_[i].first != p->kEmptyKey_) {
324       p->cells_[i].~value_type();
325     }
326   }
327   p->~AtomicHashArray();
328
329   Allocator().deallocate((char *)p, sz);
330 }
331
332 // clear -- clears all keys and values in the map and resets all counters
333 template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
334           class Allocator, class ProbeFcn, class KeyConvertFcn>
335 void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
336                      Allocator, ProbeFcn, KeyConvertFcn>::
337 clear() {
338   FOR_EACH_RANGE(i, 0, capacity_) {
339     if (cells_[i].first != kEmptyKey_) {
340       cells_[i].~value_type();
341       *const_cast<KeyT*>(&cells_[i].first) = kEmptyKey_;
342     }
343     CHECK(cells_[i].first == kEmptyKey_);
344   }
345   numEntries_.set(0);
346   numPendingEntries_.set(0);
347   isFull_.store(0, std::memory_order_relaxed);
348   numErases_.store(0, std::memory_order_relaxed);
349 }
350
351
352 // Iterator implementation
353
354 template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
355           class Allocator, class ProbeFcn, class KeyConvertFcn>
356 template <class ContT, class IterVal>
357 struct AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
358                        Allocator, ProbeFcn, KeyConvertFcn>::
359     aha_iterator
360     : boost::iterator_facade<aha_iterator<ContT,IterVal>,
361                              IterVal,
362                              boost::forward_traversal_tag>
363 {
364   explicit aha_iterator() : aha_(0) {}
365
366   // Conversion ctor for interoperability between const_iterator and
367   // iterator.  The enable_if<> magic keeps us well-behaved for
368   // is_convertible<> (v. the iterator_facade documentation).
369   template<class OtherContT, class OtherVal>
370   aha_iterator(const aha_iterator<OtherContT,OtherVal>& o,
371                typename std::enable_if<
372                std::is_convertible<OtherVal*,IterVal*>::value >::type* = 0)
373       : aha_(o.aha_)
374       , offset_(o.offset_)
375   {}
376
377   explicit aha_iterator(ContT* array, size_t offset)
378       : aha_(array)
379       , offset_(offset)
380   {}
381
382   // Returns unique index that can be used with findAt().
383   // WARNING: The following function will fail silently for hashtable
384   // with capacity > 2^32
385   uint32_t getIndex() const { return offset_; }
386
387   void advancePastEmpty() {
388     while (offset_ < aha_->capacity_ && !isValid()) {
389       ++offset_;
390     }
391   }
392
393  private:
394   friend class AtomicHashArray;
395   friend class boost::iterator_core_access;
396
397   void increment() {
398     ++offset_;
399     advancePastEmpty();
400   }
401
402   bool equal(const aha_iterator& o) const {
403     return aha_ == o.aha_ && offset_ == o.offset_;
404   }
405
406   IterVal& dereference() const {
407     return aha_->cells_[offset_];
408   }
409
410   bool isValid() const {
411     KeyT key = acquireLoadKey(aha_->cells_[offset_]);
412     return key != aha_->kEmptyKey_  &&
413       key != aha_->kLockedKey_ &&
414       key != aha_->kErasedKey_;
415   }
416
417  private:
418   ContT* aha_;
419   size_t offset_;
420 }; // aha_iterator
421
422 } // namespace folly