Add a LICENSE file for folly
[folly.git] / folly / AtomicHashArray.h
1 /*
2  * Copyright 2012 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 /**
18  *  AtomicHashArray is the building block for AtomicHashMap.  It provides the
19  *  core lock-free functionality, but is limitted by the fact that it cannot
20  *  grow past it's initialization size and is a little more awkward (no public
21  *  constructor, for example).  If you're confident that you won't run out of
22  *  space, don't mind the awkardness, and really need bare-metal performance,
23  *  feel free to use AHA directly.
24  *
25  *  Check out AtomicHashMap.h for more thorough documentation on perf and
26  *  general pros and cons relative to other hash maps.
27  *
28  *  @author Spencer Ahrens <sahrens@fb.com>
29  *  @author Jordan DeLong <delong.j@fb.com>
30  */
31
32 #ifndef FOLLY_ATOMICHASHARRAY_H_
33 #define FOLLY_ATOMICHASHARRAY_H_
34
35 #include <atomic>
36
37 #include <boost/iterator/iterator_facade.hpp>
38 #include <boost/noncopyable.hpp>
39
40 #include "folly/Hash.h"
41 #include "folly/ThreadCachedInt.h"
42
43 namespace folly {
44
45 template <class KeyT, class ValueT, class HashFcn = std::hash<KeyT>>
46 class AtomicHashMap;
47
48 template <class KeyT, class ValueT, class HashFcn = std::hash<KeyT>>
49 class AtomicHashArray : boost::noncopyable {
50   static_assert((std::is_convertible<KeyT,int32_t>::value ||
51                  std::is_convertible<KeyT,int64_t>::value),
52              "You are trying to use AtomicHashArray with disallowed key "
53              "types.  You must use atomically compare-and-swappable integer "
54              "keys, or a different container class.");
55  public:
56   typedef KeyT                key_type;
57   typedef ValueT              mapped_type;
58   typedef std::pair<const KeyT, ValueT> value_type;
59   typedef std::size_t         size_type;
60   typedef std::ptrdiff_t      difference_type;
61   typedef value_type&         reference;
62   typedef const value_type&   const_reference;
63   typedef value_type*         pointer;
64   typedef const value_type*   const_pointer;
65
66   const size_t  capacity_;
67   const size_t  maxEntries_;
68   const KeyT    kEmptyKey_;
69   const KeyT    kLockedKey_;
70   const KeyT    kErasedKey_;
71
72   template<class ContT, class IterVal>
73   struct aha_iterator;
74
75   typedef aha_iterator<const AtomicHashArray,const value_type> const_iterator;
76   typedef aha_iterator<AtomicHashArray,value_type> iterator;
77
78   // You really shouldn't need this if you use the SmartPtr provided by create,
79   // but if you really want to do something crazy like stick the released
80   // pointer into a DescriminatedPtr or something, you'll need this to clean up
81   // after yourself.
82   static void destroy(AtomicHashArray*);
83
84  private:
85   const size_t  kAnchorMask_;
86
87   struct Deleter {
88     void operator()(AtomicHashArray* ptr) {
89       AtomicHashArray::destroy(ptr);
90     }
91   };
92
93  public:
94   typedef std::unique_ptr<AtomicHashArray, Deleter> SmartPtr;
95
96   /*
97    * create --
98    *
99    *   Creates AtomicHashArray objects.  Use instead of constructor/destructor.
100    *
101    *   We do things this way in order to avoid the perf penalty of a second
102    *   pointer indirection when composing these into AtomicHashMap, which needs
103    *   to store an array of pointers so that it can perform atomic operations on
104    *   them when growing.
105    *
106    *   Instead of a mess of arguments, we take a max size and a Config struct to
107    *   simulate named ctor parameters.  The Config struct has sensible defaults
108    *   for everything, but is overloaded - if you specify a positive capacity,
109    *   that will be used directly instead of computing it based on
110    *   maxLoadFactor.
111    *
112    *   Create returns an AHA::SmartPtr which is a unique_ptr with a custom
113    *   deleter to make sure everything is cleaned up properly.
114    */
115   struct Config {
116     KeyT   emptyKey;
117     KeyT   lockedKey;
118     KeyT   erasedKey;
119     double maxLoadFactor;
120     int    entryCountThreadCacheSize;
121     size_t capacity; // if positive, overrides maxLoadFactor
122
123     constexpr Config() : emptyKey(static_cast<KeyT>(-1ul)),
124                          lockedKey(static_cast<KeyT>(-2ul)),
125                          erasedKey(static_cast<KeyT>(-3ul)),
126                          maxLoadFactor(0.8),
127                          entryCountThreadCacheSize(1000),
128                          capacity(0) {}
129   };
130
131   static const Config defaultConfig;
132   static SmartPtr create(size_t maxSize, const Config& = defaultConfig);
133
134   iterator find(KeyT k) {
135     return iterator(this, findInternal(k).idx);
136   }
137   const_iterator find(KeyT k) const {
138     return const_cast<AtomicHashArray*>(this)->find(k);
139   }
140
141   /*
142    * insert --
143    *
144    *   Returns a pair with iterator to the element at r.first and bool success.
145    *   Retrieve the index with ret.first.getIndex().
146    *
147    *   Fails on key collision (does not overwrite) or if map becomes
148    *   full, at which point no element is inserted, iterator is set to end(),
149    *   and success is set false.  On collisions, success is set false, but the
150    *   iterator is set to the existing entry.
151    */
152   std::pair<iterator,bool> insert(const value_type& r) {
153     SimpleRetT ret = insertInternal(r);
154     return std::make_pair(iterator(this, ret.idx), ret.success);
155   }
156
157   // returns the number of elements erased - should never exceed 1
158   size_t erase(KeyT k);
159
160   // clears all keys and values in the map and resets all counters.  Not thread
161   // safe.
162   void clear();
163
164   // Exact number of elements in the map - note that readFull() acquires a
165   // mutex.  See folly/ThreadCachedInt.h for more details.
166   size_t size() const {
167     return numEntries_.readFull() -
168       numErases_.load(std::memory_order_relaxed);
169   }
170
171   bool empty() const { return size() == 0; }
172
173   iterator begin()             { return iterator(this, 0); }
174   iterator end()               { return iterator(this, capacity_); }
175   const_iterator begin() const { return const_iterator(this, 0); }
176   const_iterator end() const   { return const_iterator(this, capacity_); }
177
178   // See AtomicHashMap::findAt - access elements directly
179   // WARNING: The following 2 functions will fail silently for hashtable
180   // with capacity > 2^32
181   iterator findAt(uint32_t idx) {
182     DCHECK_LT(idx, capacity_);
183     return iterator(this, idx);
184   }
185   const_iterator findAt(uint32_t idx) const {
186     return const_cast<AtomicHashArray*>(this)->findAt(idx);
187   }
188
189   iterator makeIter(size_t idx) { return iterator(this, idx); }
190   const_iterator makeIter(size_t idx) const {
191     return const_iterator(this, idx);
192   }
193
194   // The max load factor allowed for this map
195   double maxLoadFactor() const { return ((double) maxEntries_) / capacity_; }
196
197   void setEntryCountThreadCacheSize(uint32_t newSize) {
198     numEntries_.setCacheSize(newSize);
199     numPendingEntries_.setCacheSize(newSize);
200   }
201
202   int getEntryCountThreadCacheSize() const {
203     return numEntries_.getCacheSize();
204   }
205
206   /* Private data and helper functions... */
207
208  private:
209   friend class AtomicHashMap<KeyT,ValueT,HashFcn>;
210
211   struct SimpleRetT { size_t idx; bool success;
212     SimpleRetT(size_t i, bool s) : idx(i), success(s) {}
213     SimpleRetT() {}
214   };
215
216   SimpleRetT insertInternal(const value_type& record);
217
218   SimpleRetT findInternal(const KeyT key);
219
220   static std::atomic<KeyT>* cellKeyPtr(const value_type& r) {
221     // We need some illegal casting here in order to actually store
222     // our value_type as a std::pair<const,>.  But a little bit of
223     // undefined behavior never hurt anyone ...
224     static_assert(sizeof(std::atomic<KeyT>) == sizeof(KeyT),
225                   "std::atomic is implemented in an unexpected way for AHM");
226     return
227       const_cast<std::atomic<KeyT>*>(
228         reinterpret_cast<std::atomic<KeyT> const*>(&r.first));
229   }
230
231   static KeyT relaxedLoadKey(const value_type& r) {
232     return cellKeyPtr(r)->load(std::memory_order_relaxed);
233   }
234
235   // Fun with thread local storage - atomic increment is expensive
236   // (relatively), so we accumulate in the thread cache and periodically
237   // flush to the actual variable, and walk through the unflushed counts when
238   // reading the value, so be careful of calling size() too frequently.  This
239   // increases insertion throughput several times over while keeping the count
240   // accurate.
241   ThreadCachedInt<int64_t> numEntries_;  // Successful key inserts
242   ThreadCachedInt<int64_t> numPendingEntries_; // Used by insertInternal
243   std::atomic<int64_t> isFull_; // Used by insertInternal
244   std::atomic<int64_t> numErases_;   // Successful key erases
245
246   value_type cells_[0];  // This must be the last field of this class
247
248   // Force constructor/destructor private since create/destroy should be
249   // used externally instead
250   AtomicHashArray(size_t capacity, KeyT emptyKey, KeyT lockedKey,
251                   KeyT erasedKey, double maxLoadFactor, size_t cacheSize);
252
253   ~AtomicHashArray() {}
254
255   inline void unlockCell(value_type* const cell, KeyT newKey) {
256     cellKeyPtr(*cell)->store(newKey, std::memory_order_release);
257   }
258
259   inline bool tryLockCell(value_type* const cell) {
260     KeyT expect = kEmptyKey_;
261     return cellKeyPtr(*cell)->compare_exchange_strong(expect, kLockedKey_,
262       std::memory_order_acquire);
263   }
264
265   inline size_t keyToAnchorIdx(const KeyT k) const {
266     const size_t hashVal = HashFcn()(k);
267     const size_t probe = hashVal & kAnchorMask_;
268     return LIKELY(probe < capacity_) ? probe : hashVal % capacity_;
269   }
270
271   inline size_t probeNext(size_t idx, size_t numProbes) {
272     //idx += numProbes; // quadratic probing
273     idx += 1; // linear probing
274     // Avoid modulus because it's slow
275     return LIKELY(idx < capacity_) ? idx : (idx - capacity_);
276   }
277 }; // AtomicHashArray
278
279 } // namespace folly
280
281 #include "AtomicHashArray-inl.h"
282
283 #endif // FOLLY_ATOMICHASHARRAY_H_