AtomicHashArray-inl.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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. #ifndef FOLLY_ATOMICHASHARRAY_H_
  17. #error "This should only be included by AtomicHashArray.h"
  18. #endif
  19. #include <type_traits>
  20. #include <folly/detail/AtomicHashUtils.h>
  21. #include <folly/lang/Bits.h>
  22. namespace folly {
  23. // AtomicHashArray private constructor --
  24. template <
  25. class KeyT,
  26. class ValueT,
  27. class HashFcn,
  28. class EqualFcn,
  29. class Allocator,
  30. class ProbeFcn,
  31. class KeyConvertFcn>
  32. AtomicHashArray<
  33. KeyT,
  34. ValueT,
  35. HashFcn,
  36. EqualFcn,
  37. Allocator,
  38. ProbeFcn,
  39. KeyConvertFcn>::
  40. AtomicHashArray(
  41. size_t capacity,
  42. KeyT emptyKey,
  43. KeyT lockedKey,
  44. KeyT erasedKey,
  45. double _maxLoadFactor,
  46. uint32_t cacheSize)
  47. : capacity_(capacity),
  48. maxEntries_(size_t(_maxLoadFactor * capacity_ + 0.5)),
  49. kEmptyKey_(emptyKey),
  50. kLockedKey_(lockedKey),
  51. kErasedKey_(erasedKey),
  52. kAnchorMask_(nextPowTwo(capacity_) - 1),
  53. numEntries_(0, cacheSize),
  54. numPendingEntries_(0, cacheSize),
  55. isFull_(0),
  56. numErases_(0) {}
  57. /*
  58. * findInternal --
  59. *
  60. * Sets ret.second to value found and ret.index to index
  61. * of key and returns true, or if key does not exist returns false and
  62. * ret.index is set to capacity_.
  63. */
  64. template <
  65. class KeyT,
  66. class ValueT,
  67. class HashFcn,
  68. class EqualFcn,
  69. class Allocator,
  70. class ProbeFcn,
  71. class KeyConvertFcn>
  72. template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
  73. typename AtomicHashArray<
  74. KeyT,
  75. ValueT,
  76. HashFcn,
  77. EqualFcn,
  78. Allocator,
  79. ProbeFcn,
  80. KeyConvertFcn>::SimpleRetT
  81. AtomicHashArray<
  82. KeyT,
  83. ValueT,
  84. HashFcn,
  85. EqualFcn,
  86. Allocator,
  87. ProbeFcn,
  88. KeyConvertFcn>::findInternal(const LookupKeyT key_in) {
  89. checkLegalKeyIfKey<LookupKeyT>(key_in);
  90. for (size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in),
  91. numProbes = 0;
  92. ;
  93. idx = ProbeFcn()(idx, numProbes, capacity_)) {
  94. const KeyT key = acquireLoadKey(cells_[idx]);
  95. if (LIKELY(LookupEqualFcn()(key, key_in))) {
  96. return SimpleRetT(idx, true);
  97. }
  98. if (UNLIKELY(key == kEmptyKey_)) {
  99. // if we hit an empty element, this key does not exist
  100. return SimpleRetT(capacity_, false);
  101. }
  102. // NOTE: the way we count numProbes must be same in find(), insert(),
  103. // and erase(). Otherwise it may break probing.
  104. ++numProbes;
  105. if (UNLIKELY(numProbes >= capacity_)) {
  106. // probed every cell...fail
  107. return SimpleRetT(capacity_, false);
  108. }
  109. }
  110. }
  111. /*
  112. * insertInternal --
  113. *
  114. * Returns false on failure due to key collision or full.
  115. * Also sets ret.index to the index of the key. If the map is full, sets
  116. * ret.index = capacity_. Also sets ret.second to cell value, thus if insert
  117. * successful this will be what we just inserted, if there is a key collision
  118. * this will be the previously inserted value, and if the map is full it is
  119. * default.
  120. */
  121. template <
  122. class KeyT,
  123. class ValueT,
  124. class HashFcn,
  125. class EqualFcn,
  126. class Allocator,
  127. class ProbeFcn,
  128. class KeyConvertFcn>
  129. template <
  130. typename LookupKeyT,
  131. typename LookupHashFcn,
  132. typename LookupEqualFcn,
  133. typename LookupKeyToKeyFcn,
  134. typename... ArgTs>
  135. typename AtomicHashArray<
  136. KeyT,
  137. ValueT,
  138. HashFcn,
  139. EqualFcn,
  140. Allocator,
  141. ProbeFcn,
  142. KeyConvertFcn>::SimpleRetT
  143. AtomicHashArray<
  144. KeyT,
  145. ValueT,
  146. HashFcn,
  147. EqualFcn,
  148. Allocator,
  149. ProbeFcn,
  150. KeyConvertFcn>::insertInternal(LookupKeyT key_in, ArgTs&&... vCtorArgs) {
  151. const short NO_NEW_INSERTS = 1;
  152. const short NO_PENDING_INSERTS = 2;
  153. checkLegalKeyIfKey<LookupKeyT>(key_in);
  154. size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in);
  155. size_t numProbes = 0;
  156. for (;;) {
  157. DCHECK_LT(idx, capacity_);
  158. value_type* cell = &cells_[idx];
  159. if (relaxedLoadKey(*cell) == kEmptyKey_) {
  160. // NOTE: isFull_ is set based on numEntries_.readFast(), so it's
  161. // possible to insert more than maxEntries_ entries. However, it's not
  162. // possible to insert past capacity_.
  163. ++numPendingEntries_;
  164. if (isFull_.load(std::memory_order_acquire)) {
  165. --numPendingEntries_;
  166. // Before deciding whether this insert succeeded, this thread needs to
  167. // wait until no other thread can add a new entry.
  168. // Correctness assumes isFull_ is true at this point. If
  169. // another thread now does ++numPendingEntries_, we expect it
  170. // to pass the isFull_.load() test above. (It shouldn't insert
  171. // a new entry.)
  172. detail::atomic_hash_spin_wait([&] {
  173. return (isFull_.load(std::memory_order_acquire) !=
  174. NO_PENDING_INSERTS) &&
  175. (numPendingEntries_.readFull() != 0);
  176. });
  177. isFull_.store(NO_PENDING_INSERTS, std::memory_order_release);
  178. if (relaxedLoadKey(*cell) == kEmptyKey_) {
  179. // Don't insert past max load factor
  180. return SimpleRetT(capacity_, false);
  181. }
  182. } else {
  183. // An unallocated cell. Try once to lock it. If we succeed, insert here.
  184. // If we fail, fall through to comparison below; maybe the insert that
  185. // just beat us was for this very key....
  186. if (tryLockCell(cell)) {
  187. KeyT key_new;
  188. // Write the value - done before unlocking
  189. try {
  190. key_new = LookupKeyToKeyFcn()(key_in);
  191. typedef
  192. typename std::remove_const<LookupKeyT>::type LookupKeyTNoConst;
  193. constexpr bool kAlreadyChecked =
  194. std::is_same<KeyT, LookupKeyTNoConst>::value;
  195. if (!kAlreadyChecked) {
  196. checkLegalKeyIfKey(key_new);
  197. }
  198. DCHECK(relaxedLoadKey(*cell) == kLockedKey_);
  199. // A const mapped_type is only constant once constructed, so cast
  200. // away any const for the placement new here.
  201. using mapped = typename std::remove_const<mapped_type>::type;
  202. new (const_cast<mapped*>(&cell->second))
  203. ValueT(std::forward<ArgTs>(vCtorArgs)...);
  204. unlockCell(cell, key_new); // Sets the new key
  205. } catch (...) {
  206. // Transition back to empty key---requires handling
  207. // locked->empty below.
  208. unlockCell(cell, kEmptyKey_);
  209. --numPendingEntries_;
  210. throw;
  211. }
  212. // An erase() can race here and delete right after our insertion
  213. // Direct comparison rather than EqualFcn ok here
  214. // (we just inserted it)
  215. DCHECK(
  216. relaxedLoadKey(*cell) == key_new ||
  217. relaxedLoadKey(*cell) == kErasedKey_);
  218. --numPendingEntries_;
  219. ++numEntries_; // This is a thread cached atomic increment :)
  220. if (numEntries_.readFast() >= maxEntries_) {
  221. isFull_.store(NO_NEW_INSERTS, std::memory_order_relaxed);
  222. }
  223. return SimpleRetT(idx, true);
  224. }
  225. --numPendingEntries_;
  226. }
  227. }
  228. DCHECK(relaxedLoadKey(*cell) != kEmptyKey_);
  229. if (kLockedKey_ == acquireLoadKey(*cell)) {
  230. detail::atomic_hash_spin_wait(
  231. [&] { return kLockedKey_ == acquireLoadKey(*cell); });
  232. }
  233. const KeyT thisKey = acquireLoadKey(*cell);
  234. if (LookupEqualFcn()(thisKey, key_in)) {
  235. // Found an existing entry for our key, but we don't overwrite the
  236. // previous value.
  237. return SimpleRetT(idx, false);
  238. } else if (thisKey == kEmptyKey_ || thisKey == kLockedKey_) {
  239. // We need to try again (i.e., don't increment numProbes or
  240. // advance idx): this case can happen if the constructor for
  241. // ValueT threw for this very cell (the rethrow block above).
  242. continue;
  243. }
  244. // NOTE: the way we count numProbes must be same in find(),
  245. // insert(), and erase(). Otherwise it may break probing.
  246. ++numProbes;
  247. if (UNLIKELY(numProbes >= capacity_)) {
  248. // probed every cell...fail
  249. return SimpleRetT(capacity_, false);
  250. }
  251. idx = ProbeFcn()(idx, numProbes, capacity_);
  252. }
  253. }
  254. /*
  255. * erase --
  256. *
  257. * This will attempt to erase the given key key_in if the key is found. It
  258. * returns 1 iff the key was located and marked as erased, and 0 otherwise.
  259. *
  260. * Memory is not freed or reclaimed by erase, i.e. the cell containing the
  261. * erased key will never be reused. If there's an associated value, we won't
  262. * touch it either.
  263. */
  264. template <
  265. class KeyT,
  266. class ValueT,
  267. class HashFcn,
  268. class EqualFcn,
  269. class Allocator,
  270. class ProbeFcn,
  271. class KeyConvertFcn>
  272. size_t AtomicHashArray<
  273. KeyT,
  274. ValueT,
  275. HashFcn,
  276. EqualFcn,
  277. Allocator,
  278. ProbeFcn,
  279. KeyConvertFcn>::erase(KeyT key_in) {
  280. CHECK_NE(key_in, kEmptyKey_);
  281. CHECK_NE(key_in, kLockedKey_);
  282. CHECK_NE(key_in, kErasedKey_);
  283. for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;;
  284. idx = ProbeFcn()(idx, numProbes, capacity_)) {
  285. DCHECK_LT(idx, capacity_);
  286. value_type* cell = &cells_[idx];
  287. KeyT currentKey = acquireLoadKey(*cell);
  288. if (currentKey == kEmptyKey_ || currentKey == kLockedKey_) {
  289. // If we hit an empty (or locked) element, this key does not exist. This
  290. // is similar to how it's handled in find().
  291. return 0;
  292. }
  293. if (EqualFcn()(currentKey, key_in)) {
  294. // Found an existing entry for our key, attempt to mark it erased.
  295. // Some other thread may have erased our key, but this is ok.
  296. KeyT expect = currentKey;
  297. if (cellKeyPtr(*cell)->compare_exchange_strong(expect, kErasedKey_)) {
  298. numErases_.fetch_add(1, std::memory_order_relaxed);
  299. // Even if there's a value in the cell, we won't delete (or even
  300. // default construct) it because some other thread may be accessing it.
  301. // Locking it meanwhile won't work either since another thread may be
  302. // holding a pointer to it.
  303. // We found the key and successfully erased it.
  304. return 1;
  305. }
  306. // If another thread succeeds in erasing our key, we'll stop our search.
  307. return 0;
  308. }
  309. // NOTE: the way we count numProbes must be same in find(), insert(),
  310. // and erase(). Otherwise it may break probing.
  311. ++numProbes;
  312. if (UNLIKELY(numProbes >= capacity_)) {
  313. // probed every cell...fail
  314. return 0;
  315. }
  316. }
  317. }
  318. template <
  319. class KeyT,
  320. class ValueT,
  321. class HashFcn,
  322. class EqualFcn,
  323. class Allocator,
  324. class ProbeFcn,
  325. class KeyConvertFcn>
  326. typename AtomicHashArray<
  327. KeyT,
  328. ValueT,
  329. HashFcn,
  330. EqualFcn,
  331. Allocator,
  332. ProbeFcn,
  333. KeyConvertFcn>::SmartPtr
  334. AtomicHashArray<
  335. KeyT,
  336. ValueT,
  337. HashFcn,
  338. EqualFcn,
  339. Allocator,
  340. ProbeFcn,
  341. KeyConvertFcn>::create(size_t maxSize, const Config& c) {
  342. CHECK_LE(c.maxLoadFactor, 1.0);
  343. CHECK_GT(c.maxLoadFactor, 0.0);
  344. CHECK_NE(c.emptyKey, c.lockedKey);
  345. size_t capacity = size_t(maxSize / c.maxLoadFactor);
  346. size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * capacity;
  347. auto const mem = Allocator().allocate(sz);
  348. try {
  349. new (mem) AtomicHashArray(
  350. capacity,
  351. c.emptyKey,
  352. c.lockedKey,
  353. c.erasedKey,
  354. c.maxLoadFactor,
  355. c.entryCountThreadCacheSize);
  356. } catch (...) {
  357. Allocator().deallocate(mem, sz);
  358. throw;
  359. }
  360. SmartPtr map(static_cast<AtomicHashArray*>((void*)mem));
  361. /*
  362. * Mark all cells as empty.
  363. *
  364. * Note: we're bending the rules a little here accessing the key
  365. * element in our cells even though the cell object has not been
  366. * constructed, and casting them to atomic objects (see cellKeyPtr).
  367. * (Also, in fact we never actually invoke the value_type
  368. * constructor.) This is in order to avoid needing to default
  369. * construct a bunch of value_type when we first start up: if you
  370. * have an expensive default constructor for the value type this can
  371. * noticeably speed construction time for an AHA.
  372. */
  373. FOR_EACH_RANGE (i, 0, map->capacity_) {
  374. cellKeyPtr(map->cells_[i])
  375. ->store(map->kEmptyKey_, std::memory_order_relaxed);
  376. }
  377. return map;
  378. }
  379. template <
  380. class KeyT,
  381. class ValueT,
  382. class HashFcn,
  383. class EqualFcn,
  384. class Allocator,
  385. class ProbeFcn,
  386. class KeyConvertFcn>
  387. void AtomicHashArray<
  388. KeyT,
  389. ValueT,
  390. HashFcn,
  391. EqualFcn,
  392. Allocator,
  393. ProbeFcn,
  394. KeyConvertFcn>::destroy(AtomicHashArray* p) {
  395. assert(p);
  396. size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * p->capacity_;
  397. FOR_EACH_RANGE (i, 0, p->capacity_) {
  398. if (p->cells_[i].first != p->kEmptyKey_) {
  399. p->cells_[i].~value_type();
  400. }
  401. }
  402. p->~AtomicHashArray();
  403. Allocator().deallocate((char*)p, sz);
  404. }
  405. // clear -- clears all keys and values in the map and resets all counters
  406. template <
  407. class KeyT,
  408. class ValueT,
  409. class HashFcn,
  410. class EqualFcn,
  411. class Allocator,
  412. class ProbeFcn,
  413. class KeyConvertFcn>
  414. void AtomicHashArray<
  415. KeyT,
  416. ValueT,
  417. HashFcn,
  418. EqualFcn,
  419. Allocator,
  420. ProbeFcn,
  421. KeyConvertFcn>::clear() {
  422. FOR_EACH_RANGE (i, 0, capacity_) {
  423. if (cells_[i].first != kEmptyKey_) {
  424. cells_[i].~value_type();
  425. *const_cast<KeyT*>(&cells_[i].first) = kEmptyKey_;
  426. }
  427. CHECK(cells_[i].first == kEmptyKey_);
  428. }
  429. numEntries_.set(0);
  430. numPendingEntries_.set(0);
  431. isFull_.store(0, std::memory_order_relaxed);
  432. numErases_.store(0, std::memory_order_relaxed);
  433. }
  434. // Iterator implementation
  435. template <
  436. class KeyT,
  437. class ValueT,
  438. class HashFcn,
  439. class EqualFcn,
  440. class Allocator,
  441. class ProbeFcn,
  442. class KeyConvertFcn>
  443. template <class ContT, class IterVal>
  444. struct AtomicHashArray<
  445. KeyT,
  446. ValueT,
  447. HashFcn,
  448. EqualFcn,
  449. Allocator,
  450. ProbeFcn,
  451. KeyConvertFcn>::aha_iterator
  452. : boost::iterator_facade<
  453. aha_iterator<ContT, IterVal>,
  454. IterVal,
  455. boost::forward_traversal_tag> {
  456. explicit aha_iterator() : aha_(nullptr) {}
  457. // Conversion ctor for interoperability between const_iterator and
  458. // iterator. The enable_if<> magic keeps us well-behaved for
  459. // is_convertible<> (v. the iterator_facade documentation).
  460. template <class OtherContT, class OtherVal>
  461. aha_iterator(
  462. const aha_iterator<OtherContT, OtherVal>& o,
  463. typename std::enable_if<
  464. std::is_convertible<OtherVal*, IterVal*>::value>::type* = nullptr)
  465. : aha_(o.aha_), offset_(o.offset_) {}
  466. explicit aha_iterator(ContT* array, size_t offset)
  467. : aha_(array), offset_(offset) {}
  468. // Returns unique index that can be used with findAt().
  469. // WARNING: The following function will fail silently for hashtable
  470. // with capacity > 2^32
  471. uint32_t getIndex() const {
  472. return offset_;
  473. }
  474. void advancePastEmpty() {
  475. while (offset_ < aha_->capacity_ && !isValid()) {
  476. ++offset_;
  477. }
  478. }
  479. private:
  480. friend class AtomicHashArray;
  481. friend class boost::iterator_core_access;
  482. void increment() {
  483. ++offset_;
  484. advancePastEmpty();
  485. }
  486. bool equal(const aha_iterator& o) const {
  487. return aha_ == o.aha_ && offset_ == o.offset_;
  488. }
  489. IterVal& dereference() const {
  490. return aha_->cells_[offset_];
  491. }
  492. bool isValid() const {
  493. KeyT key = acquireLoadKey(aha_->cells_[offset_]);
  494. return key != aha_->kEmptyKey_ && key != aha_->kLockedKey_ &&
  495. key != aha_->kErasedKey_;
  496. }
  497. private:
  498. ContT* aha_;
  499. size_t offset_;
  500. }; // aha_iterator
  501. } // namespace folly