AtomicHashMap.h 17 KB

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