Make AtomicHashMap support move constructible types
[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     return insert(r.first, r.second);
230   }
231   std::pair<iterator,bool> insert(key_type k, const mapped_type& v);
232   std::pair<iterator,bool> insert(value_type&& r) {
233     return insert(r.first, std::move(r.second));
234   }
235   std::pair<iterator,bool> insert(key_type k, mapped_type&& v);
236
237   /*
238    * find --
239    *
240    *   Returns an iterator into the map.
241    *
242    *   If the key is not found, returns end().
243    */
244   iterator find(key_type k);
245   const_iterator find(key_type k) const;
246
247   /*
248    * erase --
249    *
250    *   Erases key k from the map
251    *
252    *   Returns 1 iff the key is found and erased, and 0 otherwise.
253    */
254   size_type erase(key_type k);
255
256   /*
257    * clear --
258    *
259    *   Wipes all keys and values from primary map and destroys all secondary
260    *   maps.  Primary map remains allocated and thus the memory can be reused
261    *   in place.  Not thread safe.
262    *
263    */
264   void clear();
265
266   /*
267    * size --
268    *
269    *  Returns the exact size of the map.  Note this is not as cheap as typical
270    *  size() implementations because, for each AtomicHashArray in this AHM, we
271    *  need to grab a lock and accumulate the values from all the thread local
272    *  counters.  See folly/ThreadCachedInt.h for more details.
273    */
274   size_t size() const;
275
276   bool empty() const { return size() == 0; }
277
278   size_type count(key_type k) const {
279     return find(k) == end() ? 0 : 1;
280   }
281
282
283   /*
284    * findAt --
285    *
286    *   Returns an iterator into the map.
287    *
288    *   idx should only be an unmodified value returned by calling getIndex() on
289    *   a valid iterator returned by find() or insert(). If idx is invalid you
290    *   have a bug and the process aborts.
291    */
292   iterator findAt(uint32_t idx) {
293     SimpleRetT ret = findAtInternal(idx);
294     DCHECK_LT(ret.i, numSubMaps());
295     return iterator(this, ret.i,
296       subMaps_[ret.i].load(std::memory_order_relaxed)->makeIter(ret.j));
297   }
298   const_iterator findAt(uint32_t idx) const {
299     return const_cast<AtomicHashMap*>(this)->findAt(idx);
300   }
301
302   // Total capacity - summation of capacities of all submaps.
303   size_t capacity() const;
304
305   // Number of new insertions until current submaps are all at max load factor.
306   size_t spaceRemaining() const;
307
308   void setEntryCountThreadCacheSize(int32_t newSize) {
309     const int numMaps = numMapsAllocated_.load(std::memory_order_acquire);
310     for (int i = 0; i < numMaps; ++i) {
311       SubMap* map = subMaps_[i].load(std::memory_order_relaxed);
312       map->setEntryCountThreadCacheSize(newSize);
313     }
314   }
315
316   // Number of sub maps allocated so far to implement this map.  The more there
317   // are, the worse the performance.
318   int numSubMaps() const {
319     return numMapsAllocated_.load(std::memory_order_acquire);
320   }
321
322   iterator begin() {
323     return iterator(this, 0,
324       subMaps_[0].load(std::memory_order_relaxed)->begin());
325   }
326
327   iterator end() {
328     return iterator();
329   }
330
331   const_iterator begin() const {
332     return const_iterator(this, 0,
333       subMaps_[0].load(std::memory_order_relaxed)->begin());
334   }
335
336   const_iterator end() const {
337     return const_iterator();
338   }
339
340   /* Advanced functions for direct access: */
341
342   inline uint32_t recToIdx(const value_type& r, bool mayInsert = true) {
343     SimpleRetT ret = mayInsert ?
344       insertInternal(r.first, r.second) : findInternal(r.first);
345     return encodeIndex(ret.i, ret.j);
346   }
347
348   inline uint32_t recToIdx(value_type&& r, bool mayInsert = true) {
349     SimpleRetT ret = mayInsert ?
350       insertInternal(r.first, std::move(r.second)) : findInternal(r.first);
351     return encodeIndex(ret.i, ret.j);
352   }
353
354   inline uint32_t recToIdx(key_type k, const mapped_type& v,
355     bool mayInsert = true) {
356     SimpleRetT ret = mayInsert ? insertInternal(k, v) : findInternal(k);
357     return encodeIndex(ret.i, ret.j);
358   }
359
360   inline uint32_t recToIdx(key_type k, mapped_type&& v, bool mayInsert = true) {
361     SimpleRetT ret = mayInsert ?
362       insertInternal(k, std::move(v)) : findInternal(k);
363     return encodeIndex(ret.i, ret.j);
364   }
365
366   inline uint32_t keyToIdx(const KeyT k, bool mayInsert = false) {
367     return recToIdx(value_type(k), mayInsert);
368   }
369
370   inline const value_type& idxToRec(uint32_t idx) const {
371     SimpleRetT ret = findAtInternal(idx);
372     return subMaps_[ret.i].load(std::memory_order_relaxed)->idxToRec(ret.j);
373   }
374
375   /* Private data and helper functions... */
376
377  private:
378   // This limits primary submap size to 2^31 ~= 2 billion, secondary submap
379   // size to 2^(32 - kNumSubMapBits_ - 1) = 2^27 ~= 130 million, and num subMaps
380   // to 2^kNumSubMapBits_ = 16.
381   static const uint32_t  kNumSubMapBits_     = 4;
382   static const uint32_t  kSecondaryMapBit_   = 1u << 31; // Highest bit
383   static const uint32_t  kSubMapIndexShift_  = 32 - kNumSubMapBits_ - 1;
384   static const uint32_t  kSubMapIndexMask_   = (1 << kSubMapIndexShift_) - 1;
385   static const uint32_t  kNumSubMaps_        = 1 << kNumSubMapBits_;
386   static const uintptr_t kLockedPtr_         = 0x88ul << 48; // invalid pointer
387
388   struct SimpleRetT { uint32_t i; size_t j; bool success;
389     SimpleRetT(uint32_t ii, size_t jj, bool s) : i(ii), j(jj), success(s) {}
390     SimpleRetT() {}
391   };
392
393   template <class T>
394   SimpleRetT insertInternal(KeyT key, T&& value);
395
396   SimpleRetT findInternal(const KeyT k) const;
397
398   SimpleRetT findAtInternal(const uint32_t idx) const;
399
400   std::atomic<SubMap*> subMaps_[kNumSubMaps_];
401   std::atomic<uint32_t> numMapsAllocated_;
402
403   inline bool tryLockMap(int idx) {
404     SubMap* val = nullptr;
405     return subMaps_[idx].compare_exchange_strong(val, (SubMap*)kLockedPtr_,
406       std::memory_order_acquire);
407   }
408
409   static inline uint32_t encodeIndex(uint32_t subMap, uint32_t subMapIdx);
410
411 }; // AtomicHashMap
412
413 } // namespace folly
414
415 #include "AtomicHashMap-inl.h"
416
417 #endif // FOLLY_ATOMICHASHMAP_H_