Pull from FB rev 63ce89e2f2301e6bba44a111cc7d4218022156f6
[folly.git] / folly / AtomicHashMap.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  * AtomicHashMap --
19  *
20  * A high performance concurrent hash map with int32 or int64 keys. Supports
21  * insert, find(key), findAt(index), erase(key), size, and more.  Memory cannot
22  * be freed or reclaimed by erase.  Can grow to a maximum of about 18 times the
23  * initial capacity, but performance degrades linearly with growth. Can also be
24  * used as an object store with unique 32-bit references directly into the
25  * internal storage (retrieved with iterator::getIndex()).
26  *
27  * Advantages:
28  *    - High performance (~2-4x tbb::concurrent_hash_map in heavily
29  *      multi-threaded environments).
30  *    - Efficient memory usage if initial capacity is not over estimated
31  *      (especially for small keys and values).
32  *    - Good fragmentation properties (only allocates in large slabs which can
33  *      be reused with clear() and never move).
34  *    - Can generate unique, long-lived 32-bit references for efficient lookup
35  *      (see findAt()).
36  *
37  * Disadvantages:
38  *    - Keys must be native int32 or int64, or explicitly converted.
39  *    - Must be able to specify unique empty, locked, and erased keys
40  *    - Performance degrades linearly as size grows beyond initialization
41  *      capacity.
42  *    - Max size limit of ~18x initial size (dependent on max load factor).
43  *    - Memory is not freed or reclaimed by erase.
44  *
45  * Usage and Operation Details:
46  *   Simple performance/memory tradeoff with maxLoadFactor.  Higher load factors
47  *   give better memory utilization but probe lengths increase, reducing
48  *   performance.
49  *
50  * Implementation and Performance Details:
51  *   AHArray is a fixed size contiguous block of value_type cells.  When
52  *   writing a cell, the key is locked while the rest of the record is
53  *   written.  Once done, the cell is unlocked by setting the key.  find()
54  *   is completely wait-free and doesn't require any non-relaxed atomic
55  *   operations.  AHA cannot grow beyond initialization capacity, but is
56  *   faster because of reduced data indirection.
57  *
58  *   AHMap is a wrapper around AHArray sub-maps that allows growth and provides
59  *   an interface closer to the stl UnorderedAssociativeContainer concept. These
60  *   sub-maps are allocated on the fly and are processed in series, so the more
61  *   there are (from growing past initial capacity), the worse the performance.
62  *
63  *   Insert returns false if there is a key collision and throws if the max size
64  *   of the map is exceeded.
65  *
66  *   Benchmark performance with 8 simultaneous threads processing 1 million
67  *   unique <int64, int64> entries on a 4-core, 2.5 GHz machine:
68  *
69  *     Load Factor   Mem Efficiency   usec/Insert   usec/Find
70  *         50%             50%           0.19         0.05
71  *         85%             85%           0.20         0.06
72  *         90%             90%           0.23         0.08
73  *         95%             95%           0.27         0.10
74  *
75  *   See folly/tests/AtomicHashMapTest.cpp for more benchmarks.
76  *
77  * @author Spencer Ahrens <sahrens@fb.com>
78  * @author Jordan DeLong <delong.j@fb.com>
79  *
80  */
81
82 #ifndef FOLLY_ATOMICHASHMAP_H_
83 #define FOLLY_ATOMICHASHMAP_H_
84
85 #include <boost/iterator/iterator_facade.hpp>
86 #include <boost/noncopyable.hpp>
87 #include <boost/type_traits/is_convertible.hpp>
88 #include <glog/logging.h>
89
90 #include <stdexcept>
91 #include <functional>
92 #include <atomic>
93
94 #include "folly/AtomicHashArray.h"
95 #include "folly/Foreach.h"
96 #include "folly/Hash.h"
97 #include "folly/Likely.h"
98 #include "folly/ThreadCachedInt.h"
99
100 namespace folly {
101
102 /*
103  * AtomicHashMap provides an interface somewhat similar to the
104  * UnorderedAssociativeContainer concept in C++.  This does not
105  * exactly match this concept (or even the basic Container concept),
106  * because of some restrictions imposed by our datastructure.
107  *
108  * Specific differences (there are quite a few):
109  *
110  * - Efficiently thread safe for inserts (main point of this stuff),
111  *   wait-free for lookups.
112  *
113  * - You can erase from this container, but the cell containing the key will
114  *   not be free or reclaimed.
115  *
116  * - You can erase everything by calling clear() (and you must guarantee only
117  *   one thread can be using the container to do that).
118  *
119  * - We aren't DefaultConstructible, CopyConstructible, Assignable, or
120  *   EqualityComparable.  (Most of these are probably not something
121  *   you actually want to do with this anyway.)
122  *
123  * - We don't support the various bucket functions, rehash(),
124  *   reserve(), or equal_range().  Also no constructors taking
125  *   iterators, although this could change.
126  *
127  * - Several insertion functions, notably operator[], are not
128  *   implemented.  It is a little too easy to misuse these functions
129  *   with this container, where part of the point is that when an
130  *   insertion happens for a new key, it will atomically have the
131  *   desired value.
132  *
133  * - The map has no templated insert() taking an iterator range, but
134  *   we do provide an insert(key, value).  The latter seems more
135  *   frequently useful for this container (to avoid sprinkling
136  *   make_pair everywhere), and providing both can lead to some gross
137  *   template error messages.
138  *
139  * - Not Allocator-aware.
140  *
141  * - KeyT must be a 32 bit or 64 bit atomic integer type, and you must
142  *   define special 'locked' and 'empty' key values in the ctor
143  *
144  * - We don't take the Hash function object as an instance in the
145  *   constructor.
146  *
147  * - We don't take a Compare template parameter (since our keys must
148  *   be integers, and the underlying hash array here uses atomic
149  *   compare-and-swap instructions, we only allow normal integer
150  *   comparisons).
151  */
152
153 // Thrown when insertion fails due to running out of space for
154 // submaps.
155 struct AtomicHashMapFullError : std::runtime_error {
156   explicit AtomicHashMapFullError()
157     : std::runtime_error("AtomicHashMap is full")
158   {}
159 };
160
161 template<class KeyT, class ValueT, class HashFcn>
162 class AtomicHashMap : boost::noncopyable {
163   typedef AtomicHashArray<KeyT, ValueT, HashFcn> SubMap;
164
165  public:
166   typedef KeyT                key_type;
167   typedef ValueT              mapped_type;
168   typedef std::pair<const KeyT, ValueT> value_type;
169   typedef HashFcn             hasher;
170   typedef std::equal_to<KeyT> key_equal;
171   typedef value_type*         pointer;
172   typedef value_type&         reference;
173   typedef const value_type&   const_reference;
174   typedef std::ptrdiff_t      difference_type;
175   typedef std::size_t         size_type;
176   typedef typename SubMap::Config Config;
177
178   template<class ContT, class IterVal, class SubIt>
179   struct ahm_iterator;
180
181   typedef ahm_iterator<const AtomicHashMap,
182                        const value_type,
183                        typename SubMap::const_iterator>
184     const_iterator;
185   typedef ahm_iterator<AtomicHashMap,
186                        value_type,
187                        typename SubMap::iterator>
188     iterator;
189
190  public:
191   const float kGrowthFrac_;  // How much to grow when we run out of capacity.
192
193   // The constructor takes a finalSizeEst which is the optimal
194   // number of elements to maximize space utilization and performance,
195   // and a Config object to specify more advanced options.
196   static const Config defaultConfig;
197   explicit AtomicHashMap(size_t finalSizeEst, const Config& = defaultConfig);
198
199   ~AtomicHashMap() {
200     const int numMaps = numMapsAllocated_.load(std::memory_order_relaxed);
201     FOR_EACH_RANGE (i, 0, numMaps) {
202       SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
203       DCHECK(thisMap);
204       SubMap::destroy(thisMap);
205     }
206   }
207
208   key_equal key_eq() const { return key_eq(); }
209   hasher hash_function() const { return hasher(); }
210
211   // TODO: emplace() support would be nice.
212
213   /*
214    * insert --
215    *
216    *   Returns a pair with iterator to the element at r.first and
217    *   success.  Retrieve the index with ret.first.getIndex().
218    *
219    *   Does not overwrite on key collision, but returns an iterator to
220    *   the existing element (since this could due to a race with
221    *   another thread, it is often important to check this return
222    *   value).
223    *
224    *   Allocates new sub maps as the existing ones become full.  If
225    *   all sub maps are full, no element is inserted, and
226    *   AtomicHashMapFullError is thrown.
227    */
228   std::pair<iterator,bool> insert(const value_type& r);
229   std::pair<iterator,bool> insert(key_type k, const mapped_type& v) {
230     return insert(value_type(k, v));
231   }
232
233   /*
234    * find --
235    *
236    *   Returns an iterator into the map.
237    *
238    *   If the key is not found, returns end().
239    */
240   iterator find(key_type k);
241   const_iterator find(key_type k) const;
242
243   /*
244    * erase --
245    *
246    *   Erases key k from the map
247    *
248    *   Returns 1 iff the key is found and erased, and 0 otherwise.
249    */
250   size_type erase(key_type k);
251
252   /*
253    * clear --
254    *
255    *   Wipes all keys and values from primary map and destroys all secondary
256    *   maps.  Primary map remains allocated and thus the memory can be reused
257    *   in place.  Not thread safe.
258    *
259    */
260   void clear();
261
262   /*
263    * size --
264    *
265    *  Returns the exact size of the map.  Note this is not as cheap as typical
266    *  size() implementations because, for each AtomicHashArray in this AHM, we
267    *  need to grab a lock and accumulate the values from all the thread local
268    *  counters.  See folly/ThreadCachedInt.h for more details.
269    */
270   size_t size() const;
271
272   bool empty() const { return size() == 0; }
273
274   size_type count(key_type k) const {
275     return find(k) == end() ? 0 : 1;
276   }
277
278
279   /*
280    * findAt --
281    *
282    *   Returns an iterator into the map.
283    *
284    *   idx should only be an unmodified value returned by calling getIndex() on
285    *   a valid iterator returned by find() or insert(). If idx is invalid you
286    *   have a bug and the process aborts.
287    */
288   iterator findAt(uint32_t idx) {
289     SimpleRetT ret = findAtInternal(idx);
290     DCHECK_LT(ret.i, numSubMaps());
291     return iterator(this, ret.i,
292       subMaps_[ret.i].load(std::memory_order_relaxed)->makeIter(ret.j));
293   }
294   const_iterator findAt(uint32_t idx) const {
295     return const_cast<AtomicHashMap*>(this)->findAt(idx);
296   }
297
298   // Total capacity - summation of capacities of all submaps.
299   size_t capacity() const;
300
301   // Number of new insertions until current submaps are all at max load factor.
302   size_t spaceRemaining() const;
303
304   void setEntryCountThreadCacheSize(int32_t newSize) {
305     const int numMaps = numMapsAllocated_.load(std::memory_order_acquire);
306     for (int i = 0; i < numMaps; ++i) {
307       SubMap* map = subMaps_[i].load(std::memory_order_relaxed);
308       map->setEntryCountThreadCacheSize(newSize);
309     }
310   }
311
312   // Number of sub maps allocated so far to implement this map.  The more there
313   // are, the worse the performance.
314   int numSubMaps() const {
315     return numMapsAllocated_.load(std::memory_order_acquire);
316   }
317
318   iterator begin() {
319     return iterator(this, 0,
320       subMaps_[0].load(std::memory_order_relaxed)->begin());
321   }
322
323   iterator end() {
324     return iterator();
325   }
326
327   const_iterator begin() const {
328     return const_iterator(this, 0,
329       subMaps_[0].load(std::memory_order_relaxed)->begin());
330   }
331
332   const_iterator end() const {
333     return const_iterator();
334   }
335
336   /* Advanced functions for direct access: */
337
338   inline uint32_t recToIdx(const value_type& r, bool mayInsert = true) {
339     SimpleRetT ret = mayInsert ? insertInternal(r) : findInternal(r.first);
340     return encodeIndex(ret.i, ret.j);
341   }
342
343   inline uint32_t keyToIdx(const KeyT k, bool mayInsert = false) {
344     return recToIdx(value_type(k), mayInsert);
345   }
346
347   inline const value_type& idxToRec(uint32_t idx) const {
348     SimpleRetT ret = findAtInternal(idx);
349     return subMaps_[ret.i].load(std::memory_order_relaxed)->idxToRec(ret.j);
350   }
351
352   /* Private data and helper functions... */
353
354  private:
355   // This limits primary submap size to 2^31 ~= 2 billion, secondary submap
356   // size to 2^(32 - kNumSubMapBits_ - 1) = 2^27 ~= 130 million, and num subMaps
357   // to 2^kNumSubMapBits_ = 16.
358   static const uint32_t  kNumSubMapBits_     = 4;
359   static const uint32_t  kSecondaryMapBit_   = 1u << 31; // Highest bit
360   static const uint32_t  kSubMapIndexShift_  = 32 - kNumSubMapBits_ - 1;
361   static const uint32_t  kSubMapIndexMask_   = (1 << kSubMapIndexShift_) - 1;
362   static const uint32_t  kNumSubMaps_        = 1 << kNumSubMapBits_;
363   static const uintptr_t kLockedPtr_         = 0x88ul << 48; // invalid pointer
364
365   struct SimpleRetT { uint32_t i; size_t j; bool success;
366     SimpleRetT(uint32_t ii, size_t jj, bool s) : i(ii), j(jj), success(s) {}
367     SimpleRetT() {}
368   };
369
370   SimpleRetT insertInternal(const value_type& r);
371
372   SimpleRetT findInternal(const KeyT k) const;
373
374   SimpleRetT findAtInternal(const uint32_t idx) const;
375
376   std::atomic<SubMap*> subMaps_[kNumSubMaps_];
377   std::atomic<uint32_t> numMapsAllocated_;
378
379   inline bool tryLockMap(int idx) {
380     SubMap* val = nullptr;
381     return subMaps_[idx].compare_exchange_strong(val, (SubMap*)kLockedPtr_,
382       std::memory_order_acquire);
383   }
384
385   static inline uint32_t encodeIndex(uint32_t subMap, uint32_t subMapIdx);
386
387 }; // AtomicHashMap
388
389 } // namespace folly
390
391 #include "AtomicHashMap-inl.h"
392
393 #endif // FOLLY_ATOMICHASHMAP_H_