IndexedMemPool.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. /*
  2. * Copyright 2014-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. #pragma once
  17. #include <assert.h>
  18. #include <errno.h>
  19. #include <stdint.h>
  20. #include <type_traits>
  21. #include <boost/noncopyable.hpp>
  22. #include <folly/Portability.h>
  23. #include <folly/concurrency/CacheLocality.h>
  24. #include <folly/portability/SysMman.h>
  25. #include <folly/portability/Unistd.h>
  26. #include <folly/synchronization/AtomicStruct.h>
  27. // Ignore shadowing warnings within this file, so includers can use -Wshadow.
  28. FOLLY_PUSH_WARNING
  29. FOLLY_GNU_DISABLE_WARNING("-Wshadow")
  30. namespace folly {
  31. namespace detail {
  32. template <typename Pool>
  33. struct IndexedMemPoolRecycler;
  34. } // namespace detail
  35. template <
  36. typename T,
  37. bool EagerRecycleWhenTrivial = false,
  38. bool EagerRecycleWhenNotTrivial = true>
  39. struct IndexedMemPoolTraits {
  40. static constexpr bool eagerRecycle() {
  41. return std::is_trivial<T>::value ? EagerRecycleWhenTrivial
  42. : EagerRecycleWhenNotTrivial;
  43. }
  44. /// Called when the element pointed to by ptr is allocated for the
  45. /// first time.
  46. static void initialize(T* ptr) {
  47. if (!eagerRecycle()) {
  48. new (ptr) T();
  49. }
  50. }
  51. /// Called when the element pointed to by ptr is freed at the pool
  52. /// destruction time.
  53. static void cleanup(T* ptr) {
  54. if (!eagerRecycle()) {
  55. ptr->~T();
  56. }
  57. }
  58. /// Called when the element is allocated with the arguments forwarded from
  59. /// IndexedMemPool::allocElem.
  60. template <typename... Args>
  61. static void onAllocate(T* ptr, Args&&... args) {
  62. static_assert(
  63. sizeof...(Args) == 0 || eagerRecycle(),
  64. "emplace-style allocation requires eager recycle, "
  65. "which is defaulted only for non-trivial types");
  66. if (eagerRecycle()) {
  67. new (ptr) T(std::forward<Args>(args)...);
  68. }
  69. }
  70. /// Called when the element is recycled.
  71. static void onRecycle(T* ptr) {
  72. if (eagerRecycle()) {
  73. ptr->~T();
  74. }
  75. }
  76. };
  77. /// IndexedMemPool traits that implements the lazy lifecycle strategy. In this
  78. /// strategy elements are default-constructed the first time they are allocated,
  79. /// and destroyed when the pool itself is destroyed.
  80. template <typename T>
  81. using IndexedMemPoolTraitsLazyRecycle = IndexedMemPoolTraits<T, false, false>;
  82. /// IndexedMemPool traits that implements the eager lifecycle strategy. In this
  83. /// strategy elements are constructed when they are allocated from the pool and
  84. /// destroyed when recycled.
  85. template <typename T>
  86. using IndexedMemPoolTraitsEagerRecycle = IndexedMemPoolTraits<T, true, true>;
  87. /// Instances of IndexedMemPool dynamically allocate and then pool their
  88. /// element type (T), returning 4-byte integer indices that can be passed
  89. /// to the pool's operator[] method to access or obtain pointers to the
  90. /// actual elements. The memory backing items returned from the pool
  91. /// will always be readable, even if items have been returned to the pool.
  92. /// These two features are useful for lock-free algorithms. The indexing
  93. /// behavior makes it easy to build tagged pointer-like-things, since
  94. /// a large number of elements can be managed using fewer bits than a
  95. /// full pointer. The access-after-free behavior makes it safe to read
  96. /// from T-s even after they have been recycled, since it is guaranteed
  97. /// that the memory won't have been returned to the OS and unmapped
  98. /// (the algorithm must still use a mechanism to validate that the read
  99. /// was correct, but it doesn't have to worry about page faults), and if
  100. /// the elements use internal sequence numbers it can be guaranteed that
  101. /// there won't be an ABA match due to the element being overwritten with
  102. /// a different type that has the same bit pattern.
  103. ///
  104. /// The object lifecycle strategy is controlled by the Traits parameter.
  105. /// One strategy, implemented by IndexedMemPoolTraitsEagerRecycle, is to
  106. /// construct objects when they are allocated from the pool and destroy
  107. /// them when they are recycled. In this mode allocIndex and allocElem
  108. /// have emplace-like semantics. In another strategy, implemented by
  109. /// IndexedMemPoolTraitsLazyRecycle, objects are default-constructed the
  110. /// first time they are removed from the pool, and deleted when the pool
  111. /// itself is deleted. By default the first mode is used for non-trivial
  112. /// T, and the second is used for trivial T. Clients can customize the
  113. /// object lifecycle by providing their own Traits implementation.
  114. /// See IndexedMemPoolTraits for a Traits example.
  115. ///
  116. /// IMPORTANT: Space for extra elements is allocated to account for those
  117. /// that are inaccessible because they are in other local lists, so the
  118. /// actual number of items that can be allocated ranges from capacity to
  119. /// capacity + (NumLocalLists_-1)*LocalListLimit_. This is important if
  120. /// you are trying to maximize the capacity of the pool while constraining
  121. /// the bit size of the resulting pointers, because the pointers will
  122. /// actually range up to the boosted capacity. See maxIndexForCapacity
  123. /// and capacityForMaxIndex.
  124. ///
  125. /// To avoid contention, NumLocalLists_ free lists of limited (less than
  126. /// or equal to LocalListLimit_) size are maintained, and each thread
  127. /// retrieves and returns entries from its associated local list. If the
  128. /// local list becomes too large then elements are placed in bulk in a
  129. /// global free list. This allows items to be efficiently recirculated
  130. /// from consumers to producers. AccessSpreader is used to access the
  131. /// local lists, so there is no performance advantage to having more
  132. /// local lists than L1 caches.
  133. ///
  134. /// The pool mmap-s the entire necessary address space when the pool is
  135. /// constructed, but delays element construction. This means that only
  136. /// elements that are actually returned to the caller get paged into the
  137. /// process's resident set (RSS).
  138. template <
  139. typename T,
  140. uint32_t NumLocalLists_ = 32,
  141. uint32_t LocalListLimit_ = 200,
  142. template <typename> class Atom = std::atomic,
  143. typename Traits = IndexedMemPoolTraits<T>>
  144. struct IndexedMemPool : boost::noncopyable {
  145. typedef T value_type;
  146. typedef std::unique_ptr<T, detail::IndexedMemPoolRecycler<IndexedMemPool>>
  147. UniquePtr;
  148. static_assert(LocalListLimit_ <= 255, "LocalListLimit must fit in 8 bits");
  149. enum {
  150. NumLocalLists = NumLocalLists_,
  151. LocalListLimit = LocalListLimit_,
  152. };
  153. // these are public because clients may need to reason about the number
  154. // of bits required to hold indices from a pool, given its capacity
  155. static constexpr uint32_t maxIndexForCapacity(uint32_t capacity) {
  156. // index of std::numeric_limits<uint32_t>::max() is reserved for isAllocated
  157. // tracking
  158. return uint32_t(std::min(
  159. uint64_t(capacity) + (NumLocalLists - 1) * LocalListLimit,
  160. uint64_t(std::numeric_limits<uint32_t>::max() - 1)));
  161. }
  162. static constexpr uint32_t capacityForMaxIndex(uint32_t maxIndex) {
  163. return maxIndex - (NumLocalLists - 1) * LocalListLimit;
  164. }
  165. /// Constructs a pool that can allocate at least _capacity_ elements,
  166. /// even if all the local lists are full
  167. explicit IndexedMemPool(uint32_t capacity)
  168. : actualCapacity_(maxIndexForCapacity(capacity)),
  169. size_(0),
  170. globalHead_(TaggedPtr{}) {
  171. const size_t needed = sizeof(Slot) * (actualCapacity_ + 1);
  172. size_t pagesize = size_t(sysconf(_SC_PAGESIZE));
  173. mmapLength_ = ((needed - 1) & ~(pagesize - 1)) + pagesize;
  174. assert(needed <= mmapLength_ && mmapLength_ < needed + pagesize);
  175. assert((mmapLength_ % pagesize) == 0);
  176. slots_ = static_cast<Slot*>(mmap(
  177. nullptr,
  178. mmapLength_,
  179. PROT_READ | PROT_WRITE,
  180. MAP_PRIVATE | MAP_ANONYMOUS,
  181. -1,
  182. 0));
  183. if (slots_ == MAP_FAILED) {
  184. assert(errno == ENOMEM);
  185. throw std::bad_alloc();
  186. }
  187. }
  188. /// Destroys all of the contained elements
  189. ~IndexedMemPool() {
  190. for (uint32_t i = maxAllocatedIndex(); i > 0; --i) {
  191. Traits::cleanup(&slots_[i].elem);
  192. }
  193. munmap(slots_, mmapLength_);
  194. }
  195. /// Returns a lower bound on the number of elements that may be
  196. /// simultaneously allocated and not yet recycled. Because of the
  197. /// local lists it is possible that more elements than this are returned
  198. /// successfully
  199. uint32_t capacity() {
  200. return capacityForMaxIndex(actualCapacity_);
  201. }
  202. /// Returns the maximum index of elements ever allocated in this pool
  203. /// including elements that have been recycled.
  204. uint32_t maxAllocatedIndex() const {
  205. // Take the minimum since it is possible that size_ > actualCapacity_.
  206. // This can happen if there are multiple concurrent requests
  207. // when size_ == actualCapacity_ - 1.
  208. return std::min(uint32_t(size_), uint32_t(actualCapacity_));
  209. }
  210. /// Finds a slot with a non-zero index, emplaces a T there if we're
  211. /// using the eager recycle lifecycle mode, and returns the index,
  212. /// or returns 0 if no elements are available. Passes a pointer to
  213. /// the element to Traits::onAllocate before the slot is marked as
  214. /// allocated.
  215. template <typename... Args>
  216. uint32_t allocIndex(Args&&... args) {
  217. auto idx = localPop(localHead());
  218. if (idx != 0) {
  219. Slot& s = slot(idx);
  220. Traits::onAllocate(&s.elem, std::forward<Args>(args)...);
  221. markAllocated(s);
  222. }
  223. return idx;
  224. }
  225. /// If an element is available, returns a std::unique_ptr to it that will
  226. /// recycle the element to the pool when it is reclaimed, otherwise returns
  227. /// a null (falsy) std::unique_ptr. Passes a pointer to the element to
  228. /// Traits::onAllocate before the slot is marked as allocated.
  229. template <typename... Args>
  230. UniquePtr allocElem(Args&&... args) {
  231. auto idx = allocIndex(std::forward<Args>(args)...);
  232. T* ptr = idx == 0 ? nullptr : &slot(idx).elem;
  233. return UniquePtr(ptr, typename UniquePtr::deleter_type(this));
  234. }
  235. /// Gives up ownership previously granted by alloc()
  236. void recycleIndex(uint32_t idx) {
  237. assert(isAllocated(idx));
  238. localPush(localHead(), idx);
  239. }
  240. /// Provides access to the pooled element referenced by idx
  241. T& operator[](uint32_t idx) {
  242. return slot(idx).elem;
  243. }
  244. /// Provides access to the pooled element referenced by idx
  245. const T& operator[](uint32_t idx) const {
  246. return slot(idx).elem;
  247. }
  248. /// If elem == &pool[idx], then pool.locateElem(elem) == idx. Also,
  249. /// pool.locateElem(nullptr) == 0
  250. uint32_t locateElem(const T* elem) const {
  251. if (!elem) {
  252. return 0;
  253. }
  254. static_assert(std::is_standard_layout<Slot>::value, "offsetof needs POD");
  255. auto slot = reinterpret_cast<const Slot*>(
  256. reinterpret_cast<const char*>(elem) - offsetof(Slot, elem));
  257. auto rv = uint32_t(slot - slots_);
  258. // this assert also tests that rv is in range
  259. assert(elem == &(*this)[rv]);
  260. return rv;
  261. }
  262. /// Returns true iff idx has been alloc()ed and not recycleIndex()ed
  263. bool isAllocated(uint32_t idx) const {
  264. return slot(idx).localNext.load(std::memory_order_acquire) == uint32_t(-1);
  265. }
  266. private:
  267. ///////////// types
  268. struct Slot {
  269. T elem;
  270. Atom<uint32_t> localNext;
  271. Atom<uint32_t> globalNext;
  272. Slot() : localNext{}, globalNext{} {}
  273. };
  274. struct TaggedPtr {
  275. uint32_t idx;
  276. // size is bottom 8 bits, tag in top 24. g++'s code generation for
  277. // bitfields seems to depend on the phase of the moon, plus we can
  278. // do better because we can rely on other checks to avoid masking
  279. uint32_t tagAndSize;
  280. enum : uint32_t {
  281. SizeBits = 8,
  282. SizeMask = (1U << SizeBits) - 1,
  283. TagIncr = 1U << SizeBits,
  284. };
  285. uint32_t size() const {
  286. return tagAndSize & SizeMask;
  287. }
  288. TaggedPtr withSize(uint32_t repl) const {
  289. assert(repl <= LocalListLimit);
  290. return TaggedPtr{idx, (tagAndSize & ~SizeMask) | repl};
  291. }
  292. TaggedPtr withSizeIncr() const {
  293. assert(size() < LocalListLimit);
  294. return TaggedPtr{idx, tagAndSize + 1};
  295. }
  296. TaggedPtr withSizeDecr() const {
  297. assert(size() > 0);
  298. return TaggedPtr{idx, tagAndSize - 1};
  299. }
  300. TaggedPtr withIdx(uint32_t repl) const {
  301. return TaggedPtr{repl, tagAndSize + TagIncr};
  302. }
  303. TaggedPtr withEmpty() const {
  304. return withIdx(0).withSize(0);
  305. }
  306. };
  307. struct alignas(hardware_destructive_interference_size) LocalList {
  308. AtomicStruct<TaggedPtr, Atom> head;
  309. LocalList() : head(TaggedPtr{}) {}
  310. };
  311. ////////// fields
  312. /// the number of bytes allocated from mmap, which is a multiple of
  313. /// the page size of the machine
  314. size_t mmapLength_;
  315. /// the actual number of slots that we will allocate, to guarantee
  316. /// that we will satisfy the capacity requested at construction time.
  317. /// They will be numbered 1..actualCapacity_ (note the 1-based counting),
  318. /// and occupy slots_[1..actualCapacity_].
  319. uint32_t actualCapacity_;
  320. /// this records the number of slots that have actually been constructed.
  321. /// To allow use of atomic ++ instead of CAS, we let this overflow.
  322. /// The actual number of constructed elements is min(actualCapacity_,
  323. /// size_)
  324. Atom<uint32_t> size_;
  325. /// raw storage, only 1..min(size_,actualCapacity_) (inclusive) are
  326. /// actually constructed. Note that slots_[0] is not constructed or used
  327. alignas(hardware_destructive_interference_size) Slot* slots_;
  328. /// use AccessSpreader to find your list. We use stripes instead of
  329. /// thread-local to avoid the need to grow or shrink on thread start
  330. /// or join. These are heads of lists chained with localNext
  331. LocalList local_[NumLocalLists];
  332. /// this is the head of a list of node chained by globalNext, that are
  333. /// themselves each the head of a list chained by localNext
  334. alignas(hardware_destructive_interference_size)
  335. AtomicStruct<TaggedPtr, Atom> globalHead_;
  336. ///////////// private methods
  337. uint32_t slotIndex(uint32_t idx) const {
  338. assert(
  339. 0 < idx && idx <= actualCapacity_ &&
  340. idx <= size_.load(std::memory_order_acquire));
  341. return idx;
  342. }
  343. Slot& slot(uint32_t idx) {
  344. return slots_[slotIndex(idx)];
  345. }
  346. const Slot& slot(uint32_t idx) const {
  347. return slots_[slotIndex(idx)];
  348. }
  349. // localHead references a full list chained by localNext. s should
  350. // reference slot(localHead), it is passed as a micro-optimization
  351. void globalPush(Slot& s, uint32_t localHead) {
  352. while (true) {
  353. TaggedPtr gh = globalHead_.load(std::memory_order_acquire);
  354. s.globalNext.store(gh.idx, std::memory_order_relaxed);
  355. if (globalHead_.compare_exchange_strong(gh, gh.withIdx(localHead))) {
  356. // success
  357. return;
  358. }
  359. }
  360. }
  361. // idx references a single node
  362. void localPush(AtomicStruct<TaggedPtr, Atom>& head, uint32_t idx) {
  363. Slot& s = slot(idx);
  364. TaggedPtr h = head.load(std::memory_order_acquire);
  365. while (true) {
  366. s.localNext.store(h.idx, std::memory_order_release);
  367. Traits::onRecycle(&slot(idx).elem);
  368. if (h.size() == LocalListLimit) {
  369. // push will overflow local list, steal it instead
  370. if (head.compare_exchange_strong(h, h.withEmpty())) {
  371. // steal was successful, put everything in the global list
  372. globalPush(s, idx);
  373. return;
  374. }
  375. } else {
  376. // local list has space
  377. if (head.compare_exchange_strong(h, h.withIdx(idx).withSizeIncr())) {
  378. // success
  379. return;
  380. }
  381. }
  382. // h was updated by failing CAS
  383. }
  384. }
  385. // returns 0 if empty
  386. uint32_t globalPop() {
  387. while (true) {
  388. TaggedPtr gh = globalHead_.load(std::memory_order_acquire);
  389. if (gh.idx == 0 ||
  390. globalHead_.compare_exchange_strong(
  391. gh,
  392. gh.withIdx(
  393. slot(gh.idx).globalNext.load(std::memory_order_relaxed)))) {
  394. // global list is empty, or pop was successful
  395. return gh.idx;
  396. }
  397. }
  398. }
  399. // returns 0 if allocation failed
  400. uint32_t localPop(AtomicStruct<TaggedPtr, Atom>& head) {
  401. while (true) {
  402. TaggedPtr h = head.load(std::memory_order_acquire);
  403. if (h.idx != 0) {
  404. // local list is non-empty, try to pop
  405. Slot& s = slot(h.idx);
  406. auto next = s.localNext.load(std::memory_order_relaxed);
  407. if (head.compare_exchange_strong(h, h.withIdx(next).withSizeDecr())) {
  408. // success
  409. return h.idx;
  410. }
  411. continue;
  412. }
  413. uint32_t idx = globalPop();
  414. if (idx == 0) {
  415. // global list is empty, allocate and construct new slot
  416. if (size_.load(std::memory_order_relaxed) >= actualCapacity_ ||
  417. (idx = ++size_) > actualCapacity_) {
  418. // allocation failed
  419. return 0;
  420. }
  421. Traits::initialize(&slot(idx).elem);
  422. return idx;
  423. }
  424. Slot& s = slot(idx);
  425. auto next = s.localNext.load(std::memory_order_relaxed);
  426. if (head.compare_exchange_strong(
  427. h, h.withIdx(next).withSize(LocalListLimit))) {
  428. // global list moved to local list, keep head for us
  429. return idx;
  430. }
  431. // local bulk push failed, return idx to the global list and try again
  432. globalPush(s, idx);
  433. }
  434. }
  435. AtomicStruct<TaggedPtr, Atom>& localHead() {
  436. auto stripe = AccessSpreader<Atom>::current(NumLocalLists);
  437. return local_[stripe].head;
  438. }
  439. void markAllocated(Slot& slot) {
  440. slot.localNext.store(uint32_t(-1), std::memory_order_release);
  441. }
  442. public:
  443. static constexpr std::size_t kSlotSize = sizeof(Slot);
  444. };
  445. namespace detail {
  446. /// This is a stateful Deleter functor, which allows std::unique_ptr
  447. /// to track elements allocated from an IndexedMemPool by tracking the
  448. /// associated pool. See IndexedMemPool::allocElem.
  449. template <typename Pool>
  450. struct IndexedMemPoolRecycler {
  451. Pool* pool;
  452. explicit IndexedMemPoolRecycler(Pool* pool) : pool(pool) {}
  453. IndexedMemPoolRecycler(const IndexedMemPoolRecycler<Pool>& rhs) = default;
  454. IndexedMemPoolRecycler& operator=(const IndexedMemPoolRecycler<Pool>& rhs) =
  455. default;
  456. void operator()(typename Pool::value_type* elem) const {
  457. pool->recycleIndex(pool->locateElem(elem));
  458. }
  459. };
  460. } // namespace detail
  461. } // namespace folly
  462. FOLLY_POP_WARNING