AtomicHashArray.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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. * AtomicHashArray is the building block for AtomicHashMap. It provides the
  18. * core lock-free functionality, but is limited by the fact that it cannot
  19. * grow past its initialization size and is a little more awkward (no public
  20. * constructor, for example). If you're confident that you won't run out of
  21. * space, don't mind the awkardness, and really need bare-metal performance,
  22. * feel free to use AHA directly.
  23. *
  24. * Check out AtomicHashMap.h for more thorough documentation on perf and
  25. * general pros and cons relative to other hash maps.
  26. *
  27. * @author Spencer Ahrens <sahrens@fb.com>
  28. * @author Jordan DeLong <delong.j@fb.com>
  29. */
  30. #pragma once
  31. #define FOLLY_ATOMICHASHARRAY_H_
  32. #include <atomic>
  33. #include <boost/iterator/iterator_facade.hpp>
  34. #include <boost/noncopyable.hpp>
  35. #include <folly/ThreadCachedInt.h>
  36. #include <folly/Utility.h>
  37. #include <folly/hash/Hash.h>
  38. namespace folly {
  39. struct AtomicHashArrayLinearProbeFcn {
  40. inline size_t operator()(size_t idx, size_t /* numProbes */, size_t capacity)
  41. const {
  42. idx += 1; // linear probing
  43. // Avoid modulus because it's slow
  44. return LIKELY(idx < capacity) ? idx : (idx - capacity);
  45. }
  46. };
  47. struct AtomicHashArrayQuadraticProbeFcn {
  48. inline size_t operator()(size_t idx, size_t numProbes, size_t capacity)
  49. const {
  50. idx += numProbes; // quadratic probing
  51. // Avoid modulus because it's slow
  52. return LIKELY(idx < capacity) ? idx : (idx - capacity);
  53. }
  54. };
  55. // Enables specializing checkLegalKey without specializing its class.
  56. namespace detail {
  57. template <typename NotKeyT, typename KeyT>
  58. inline void checkLegalKeyIfKeyTImpl(
  59. NotKeyT /* ignored */,
  60. KeyT /* emptyKey */,
  61. KeyT /* lockedKey */,
  62. KeyT /* erasedKey */) {}
  63. template <typename KeyT>
  64. inline void checkLegalKeyIfKeyTImpl(
  65. KeyT key_in,
  66. KeyT emptyKey,
  67. KeyT lockedKey,
  68. KeyT erasedKey) {
  69. DCHECK_NE(key_in, emptyKey);
  70. DCHECK_NE(key_in, lockedKey);
  71. DCHECK_NE(key_in, erasedKey);
  72. }
  73. } // namespace detail
  74. template <
  75. class KeyT,
  76. class ValueT,
  77. class HashFcn = std::hash<KeyT>,
  78. class EqualFcn = std::equal_to<KeyT>,
  79. class Allocator = std::allocator<char>,
  80. class ProbeFcn = AtomicHashArrayLinearProbeFcn,
  81. class KeyConvertFcn = Identity>
  82. class AtomicHashMap;
  83. template <
  84. class KeyT,
  85. class ValueT,
  86. class HashFcn = std::hash<KeyT>,
  87. class EqualFcn = std::equal_to<KeyT>,
  88. class Allocator = std::allocator<char>,
  89. class ProbeFcn = AtomicHashArrayLinearProbeFcn,
  90. class KeyConvertFcn = Identity>
  91. class AtomicHashArray : boost::noncopyable {
  92. static_assert(
  93. (std::is_convertible<KeyT, int32_t>::value ||
  94. std::is_convertible<KeyT, int64_t>::value ||
  95. std::is_convertible<KeyT, const void*>::value),
  96. "You are trying to use AtomicHashArray with disallowed key "
  97. "types. You must use atomically compare-and-swappable integer "
  98. "keys, or a different container class.");
  99. public:
  100. typedef KeyT key_type;
  101. typedef ValueT mapped_type;
  102. typedef HashFcn hasher;
  103. typedef EqualFcn key_equal;
  104. typedef KeyConvertFcn key_convert;
  105. typedef std::pair<const KeyT, ValueT> value_type;
  106. typedef std::size_t size_type;
  107. typedef std::ptrdiff_t difference_type;
  108. typedef value_type& reference;
  109. typedef const value_type& const_reference;
  110. typedef value_type* pointer;
  111. typedef const value_type* const_pointer;
  112. const size_t capacity_;
  113. const size_t maxEntries_;
  114. const KeyT kEmptyKey_;
  115. const KeyT kLockedKey_;
  116. const KeyT kErasedKey_;
  117. template <class ContT, class IterVal>
  118. struct aha_iterator;
  119. typedef aha_iterator<const AtomicHashArray, const value_type> const_iterator;
  120. typedef aha_iterator<AtomicHashArray, value_type> iterator;
  121. // You really shouldn't need this if you use the SmartPtr provided by create,
  122. // but if you really want to do something crazy like stick the released
  123. // pointer into a DescriminatedPtr or something, you'll need this to clean up
  124. // after yourself.
  125. static void destroy(AtomicHashArray*);
  126. private:
  127. const size_t kAnchorMask_;
  128. struct Deleter {
  129. void operator()(AtomicHashArray* ptr) {
  130. AtomicHashArray::destroy(ptr);
  131. }
  132. };
  133. public:
  134. typedef std::unique_ptr<AtomicHashArray, Deleter> SmartPtr;
  135. /*
  136. * create --
  137. *
  138. * Creates AtomicHashArray objects. Use instead of constructor/destructor.
  139. *
  140. * We do things this way in order to avoid the perf penalty of a second
  141. * pointer indirection when composing these into AtomicHashMap, which needs
  142. * to store an array of pointers so that it can perform atomic operations on
  143. * them when growing.
  144. *
  145. * Instead of a mess of arguments, we take a max size and a Config struct to
  146. * simulate named ctor parameters. The Config struct has sensible defaults
  147. * for everything, but is overloaded - if you specify a positive capacity,
  148. * that will be used directly instead of computing it based on
  149. * maxLoadFactor.
  150. *
  151. * Create returns an AHA::SmartPtr which is a unique_ptr with a custom
  152. * deleter to make sure everything is cleaned up properly.
  153. */
  154. struct Config {
  155. KeyT emptyKey;
  156. KeyT lockedKey;
  157. KeyT erasedKey;
  158. double maxLoadFactor;
  159. double growthFactor;
  160. uint32_t entryCountThreadCacheSize;
  161. size_t capacity; // if positive, overrides maxLoadFactor
  162. // Cannot have constexpr ctor because some compilers rightly complain.
  163. Config()
  164. : emptyKey((KeyT)-1),
  165. lockedKey((KeyT)-2),
  166. erasedKey((KeyT)-3),
  167. maxLoadFactor(0.8),
  168. growthFactor(-1),
  169. entryCountThreadCacheSize(1000),
  170. capacity(0) {}
  171. };
  172. // Cannot have pre-instantiated const Config instance because of SIOF.
  173. static SmartPtr create(size_t maxSize, const Config& c = Config());
  174. /*
  175. * find --
  176. *
  177. *
  178. * Returns the iterator to the element if found, otherwise end().
  179. *
  180. * As an optional feature, the type of the key to look up (LookupKeyT) is
  181. * allowed to be different from the type of keys actually stored (KeyT).
  182. *
  183. * This enables use cases where materializing the key is costly and usually
  184. * redudant, e.g., canonicalizing/interning a set of strings and being able
  185. * to look up by StringPiece. To use this feature, LookupHashFcn must take
  186. * a LookupKeyT, and LookupEqualFcn must take KeyT and LookupKeyT as first
  187. * and second parameter, respectively.
  188. *
  189. * See folly/test/ArrayHashArrayTest.cpp for sample usage.
  190. */
  191. template <
  192. typename LookupKeyT = key_type,
  193. typename LookupHashFcn = hasher,
  194. typename LookupEqualFcn = key_equal>
  195. iterator find(LookupKeyT k) {
  196. return iterator(
  197. this, findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k).idx);
  198. }
  199. template <
  200. typename LookupKeyT = key_type,
  201. typename LookupHashFcn = hasher,
  202. typename LookupEqualFcn = key_equal>
  203. const_iterator find(LookupKeyT k) const {
  204. return const_cast<AtomicHashArray*>(this)
  205. ->find<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
  206. }
  207. /*
  208. * insert --
  209. *
  210. * Returns a pair with iterator to the element at r.first and bool success.
  211. * Retrieve the index with ret.first.getIndex().
  212. *
  213. * Fails on key collision (does not overwrite) or if map becomes
  214. * full, at which point no element is inserted, iterator is set to end(),
  215. * and success is set false. On collisions, success is set false, but the
  216. * iterator is set to the existing entry.
  217. */
  218. std::pair<iterator, bool> insert(const value_type& r) {
  219. return emplace(r.first, r.second);
  220. }
  221. std::pair<iterator, bool> insert(value_type&& r) {
  222. return emplace(r.first, std::move(r.second));
  223. }
  224. /*
  225. * emplace --
  226. *
  227. * Same contract as insert(), but performs in-place construction
  228. * of the value type using the specified arguments.
  229. *
  230. * Also, like find(), this method optionally allows 'key_in' to have a type
  231. * different from that stored in the table; see find(). If and only if no
  232. * equal key is already present, this method converts 'key_in' to a key of
  233. * type KeyT using the provided LookupKeyToKeyFcn.
  234. */
  235. template <
  236. typename LookupKeyT = key_type,
  237. typename LookupHashFcn = hasher,
  238. typename LookupEqualFcn = key_equal,
  239. typename LookupKeyToKeyFcn = key_convert,
  240. typename... ArgTs>
  241. std::pair<iterator, bool> emplace(LookupKeyT key_in, ArgTs&&... vCtorArgs) {
  242. SimpleRetT ret = insertInternal<
  243. LookupKeyT,
  244. LookupHashFcn,
  245. LookupEqualFcn,
  246. LookupKeyToKeyFcn>(key_in, std::forward<ArgTs>(vCtorArgs)...);
  247. return std::make_pair(iterator(this, ret.idx), ret.success);
  248. }
  249. // returns the number of elements erased - should never exceed 1
  250. size_t erase(KeyT k);
  251. // clears all keys and values in the map and resets all counters. Not thread
  252. // safe.
  253. void clear();
  254. // Exact number of elements in the map - note that readFull() acquires a
  255. // mutex. See folly/ThreadCachedInt.h for more details.
  256. size_t size() const {
  257. return numEntries_.readFull() - numErases_.load(std::memory_order_relaxed);
  258. }
  259. bool empty() const {
  260. return size() == 0;
  261. }
  262. iterator begin() {
  263. iterator it(this, 0);
  264. it.advancePastEmpty();
  265. return it;
  266. }
  267. const_iterator begin() const {
  268. const_iterator it(this, 0);
  269. it.advancePastEmpty();
  270. return it;
  271. }
  272. iterator end() {
  273. return iterator(this, capacity_);
  274. }
  275. const_iterator end() const {
  276. return const_iterator(this, capacity_);
  277. }
  278. // See AtomicHashMap::findAt - access elements directly
  279. // WARNING: The following 2 functions will fail silently for hashtable
  280. // with capacity > 2^32
  281. iterator findAt(uint32_t idx) {
  282. DCHECK_LT(idx, capacity_);
  283. return iterator(this, idx);
  284. }
  285. const_iterator findAt(uint32_t idx) const {
  286. return const_cast<AtomicHashArray*>(this)->findAt(idx);
  287. }
  288. iterator makeIter(size_t idx) {
  289. return iterator(this, idx);
  290. }
  291. const_iterator makeIter(size_t idx) const {
  292. return const_iterator(this, idx);
  293. }
  294. // The max load factor allowed for this map
  295. double maxLoadFactor() const {
  296. return ((double)maxEntries_) / capacity_;
  297. }
  298. void setEntryCountThreadCacheSize(uint32_t newSize) {
  299. numEntries_.setCacheSize(newSize);
  300. numPendingEntries_.setCacheSize(newSize);
  301. }
  302. uint32_t getEntryCountThreadCacheSize() const {
  303. return numEntries_.getCacheSize();
  304. }
  305. /* Private data and helper functions... */
  306. private:
  307. friend class AtomicHashMap<
  308. KeyT,
  309. ValueT,
  310. HashFcn,
  311. EqualFcn,
  312. Allocator,
  313. ProbeFcn>;
  314. struct SimpleRetT {
  315. size_t idx;
  316. bool success;
  317. SimpleRetT(size_t i, bool s) : idx(i), success(s) {}
  318. SimpleRetT() = default;
  319. };
  320. template <
  321. typename LookupKeyT = key_type,
  322. typename LookupHashFcn = hasher,
  323. typename LookupEqualFcn = key_equal,
  324. typename LookupKeyToKeyFcn = Identity,
  325. typename... ArgTs>
  326. SimpleRetT insertInternal(LookupKeyT key, ArgTs&&... vCtorArgs);
  327. template <
  328. typename LookupKeyT = key_type,
  329. typename LookupHashFcn = hasher,
  330. typename LookupEqualFcn = key_equal>
  331. SimpleRetT findInternal(const LookupKeyT key);
  332. template <typename MaybeKeyT>
  333. void checkLegalKeyIfKey(MaybeKeyT key) {
  334. detail::checkLegalKeyIfKeyTImpl(key, kEmptyKey_, kLockedKey_, kErasedKey_);
  335. }
  336. static std::atomic<KeyT>* cellKeyPtr(const value_type& r) {
  337. // We need some illegal casting here in order to actually store
  338. // our value_type as a std::pair<const,>. But a little bit of
  339. // undefined behavior never hurt anyone ...
  340. static_assert(
  341. sizeof(std::atomic<KeyT>) == sizeof(KeyT),
  342. "std::atomic is implemented in an unexpected way for AHM");
  343. return const_cast<std::atomic<KeyT>*>(
  344. reinterpret_cast<std::atomic<KeyT> const*>(&r.first));
  345. }
  346. static KeyT relaxedLoadKey(const value_type& r) {
  347. return cellKeyPtr(r)->load(std::memory_order_relaxed);
  348. }
  349. static KeyT acquireLoadKey(const value_type& r) {
  350. return cellKeyPtr(r)->load(std::memory_order_acquire);
  351. }
  352. // Fun with thread local storage - atomic increment is expensive
  353. // (relatively), so we accumulate in the thread cache and periodically
  354. // flush to the actual variable, and walk through the unflushed counts when
  355. // reading the value, so be careful of calling size() too frequently. This
  356. // increases insertion throughput several times over while keeping the count
  357. // accurate.
  358. ThreadCachedInt<uint64_t> numEntries_; // Successful key inserts
  359. ThreadCachedInt<uint64_t> numPendingEntries_; // Used by insertInternal
  360. std::atomic<int64_t> isFull_; // Used by insertInternal
  361. std::atomic<int64_t> numErases_; // Successful key erases
  362. value_type cells_[0]; // This must be the last field of this class
  363. // Force constructor/destructor private since create/destroy should be
  364. // used externally instead
  365. AtomicHashArray(
  366. size_t capacity,
  367. KeyT emptyKey,
  368. KeyT lockedKey,
  369. KeyT erasedKey,
  370. double maxLoadFactor,
  371. uint32_t cacheSize);
  372. ~AtomicHashArray() = default;
  373. inline void unlockCell(value_type* const cell, KeyT newKey) {
  374. cellKeyPtr(*cell)->store(newKey, std::memory_order_release);
  375. }
  376. inline bool tryLockCell(value_type* const cell) {
  377. KeyT expect = kEmptyKey_;
  378. return cellKeyPtr(*cell)->compare_exchange_strong(
  379. expect, kLockedKey_, std::memory_order_acq_rel);
  380. }
  381. template <class LookupKeyT = key_type, class LookupHashFcn = hasher>
  382. inline size_t keyToAnchorIdx(const LookupKeyT k) const {
  383. const size_t hashVal = LookupHashFcn()(k);
  384. const size_t probe = hashVal & kAnchorMask_;
  385. return LIKELY(probe < capacity_) ? probe : hashVal % capacity_;
  386. }
  387. }; // AtomicHashArray
  388. } // namespace folly
  389. #include <folly/AtomicHashArray-inl.h>