local_cache.h 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. //===-- local_cache.h -------------------------------------------*- C++ -*-===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #ifndef SCUDO_LOCAL_CACHE_H_
  9. #define SCUDO_LOCAL_CACHE_H_
  10. #include "internal_defs.h"
  11. #include "list.h"
  12. #include "platform.h"
  13. #include "report.h"
  14. #include "stats.h"
  15. namespace scudo {
  16. template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache {
  17. typedef typename SizeClassAllocator::SizeClassMap SizeClassMap;
  18. typedef typename SizeClassAllocator::CompactPtrT CompactPtrT;
  19. struct TransferBatch {
  20. static const u16 MaxNumCached = SizeClassMap::MaxNumCachedHint;
  21. void setFromArray(CompactPtrT *Array, u16 N) {
  22. DCHECK_LE(N, MaxNumCached);
  23. Count = N;
  24. memcpy(Batch, Array, sizeof(Batch[0]) * Count);
  25. }
  26. void appendFromArray(CompactPtrT *Array, u16 N) {
  27. DCHECK_LE(N, MaxNumCached - Count);
  28. memcpy(Batch + Count, Array, sizeof(Batch[0]) * N);
  29. // u16 will be promoted to int by arithmetic type conversion.
  30. Count = static_cast<u16>(Count + N);
  31. }
  32. void clear() { Count = 0; }
  33. void add(CompactPtrT P) {
  34. DCHECK_LT(Count, MaxNumCached);
  35. Batch[Count++] = P;
  36. }
  37. void copyToArray(CompactPtrT *Array) const {
  38. memcpy(Array, Batch, sizeof(Batch[0]) * Count);
  39. }
  40. u16 getCount() const { return Count; }
  41. CompactPtrT get(u16 I) const {
  42. DCHECK_LE(I, Count);
  43. return Batch[I];
  44. }
  45. static u16 getMaxCached(uptr Size) {
  46. return Min(MaxNumCached, SizeClassMap::getMaxCachedHint(Size));
  47. }
  48. TransferBatch *Next;
  49. private:
  50. CompactPtrT Batch[MaxNumCached];
  51. u16 Count;
  52. };
  53. // A BatchGroup is used to collect blocks. Each group has a group id to
  54. // identify the group kind of contained blocks.
  55. struct BatchGroup {
  56. // `Next` is used by IntrusiveList.
  57. BatchGroup *Next;
  58. // The identifier of each group
  59. uptr GroupId;
  60. // Cache value of TransferBatch::getMaxCached()
  61. u16 MaxCachedPerBatch;
  62. // Number of blocks pushed into this group. This is an increment-only
  63. // counter.
  64. uptr PushedBlocks;
  65. // This is used to track how many blocks are pushed since last time we
  66. // checked `PushedBlocks`. It's useful for page releasing to determine the
  67. // usage of a BatchGroup.
  68. uptr PushedBlocksAtLastCheckpoint;
  69. // Blocks are managed by TransferBatch in a list.
  70. SinglyLinkedList<TransferBatch> Batches;
  71. };
  72. static_assert(sizeof(BatchGroup) <= sizeof(TransferBatch),
  73. "BatchGroup uses the same class size as TransferBatch");
  74. void init(GlobalStats *S, SizeClassAllocator *A) {
  75. DCHECK(isEmpty());
  76. Stats.init();
  77. if (LIKELY(S))
  78. S->link(&Stats);
  79. Allocator = A;
  80. }
  81. void destroy(GlobalStats *S) {
  82. drain();
  83. if (LIKELY(S))
  84. S->unlink(&Stats);
  85. }
  86. void *allocate(uptr ClassId) {
  87. DCHECK_LT(ClassId, NumClasses);
  88. PerClass *C = &PerClassArray[ClassId];
  89. if (C->Count == 0) {
  90. if (UNLIKELY(!refill(C, ClassId)))
  91. return nullptr;
  92. DCHECK_GT(C->Count, 0);
  93. }
  94. // We read ClassSize first before accessing Chunks because it's adjacent to
  95. // Count, while Chunks might be further off (depending on Count). That keeps
  96. // the memory accesses in close quarters.
  97. const uptr ClassSize = C->ClassSize;
  98. CompactPtrT CompactP = C->Chunks[--C->Count];
  99. Stats.add(StatAllocated, ClassSize);
  100. Stats.sub(StatFree, ClassSize);
  101. return Allocator->decompactPtr(ClassId, CompactP);
  102. }
  103. void deallocate(uptr ClassId, void *P) {
  104. CHECK_LT(ClassId, NumClasses);
  105. PerClass *C = &PerClassArray[ClassId];
  106. // We still have to initialize the cache in the event that the first heap
  107. // operation in a thread is a deallocation.
  108. initCacheMaybe(C);
  109. if (C->Count == C->MaxCount)
  110. drain(C, ClassId);
  111. // See comment in allocate() about memory accesses.
  112. const uptr ClassSize = C->ClassSize;
  113. C->Chunks[C->Count++] =
  114. Allocator->compactPtr(ClassId, reinterpret_cast<uptr>(P));
  115. Stats.sub(StatAllocated, ClassSize);
  116. Stats.add(StatFree, ClassSize);
  117. }
  118. bool isEmpty() const {
  119. for (uptr I = 0; I < NumClasses; ++I)
  120. if (PerClassArray[I].Count)
  121. return false;
  122. return true;
  123. }
  124. void drain() {
  125. // Drain BatchClassId last as createBatch can refill it.
  126. for (uptr I = 0; I < NumClasses; ++I) {
  127. if (I == BatchClassId)
  128. continue;
  129. while (PerClassArray[I].Count > 0)
  130. drain(&PerClassArray[I], I);
  131. }
  132. while (PerClassArray[BatchClassId].Count > 0)
  133. drain(&PerClassArray[BatchClassId], BatchClassId);
  134. DCHECK(isEmpty());
  135. }
  136. TransferBatch *createBatch(uptr ClassId, void *B) {
  137. if (ClassId != BatchClassId)
  138. B = allocate(BatchClassId);
  139. if (UNLIKELY(!B))
  140. reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId));
  141. return reinterpret_cast<TransferBatch *>(B);
  142. }
  143. BatchGroup *createGroup() {
  144. void *Ptr = allocate(BatchClassId);
  145. if (UNLIKELY(!Ptr))
  146. reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId));
  147. return reinterpret_cast<BatchGroup *>(Ptr);
  148. }
  149. LocalStats &getStats() { return Stats; }
  150. private:
  151. static const uptr NumClasses = SizeClassMap::NumClasses;
  152. static const uptr BatchClassId = SizeClassMap::BatchClassId;
  153. struct alignas(SCUDO_CACHE_LINE_SIZE) PerClass {
  154. u16 Count;
  155. u16 MaxCount;
  156. // Note: ClassSize is zero for the transfer batch.
  157. uptr ClassSize;
  158. CompactPtrT Chunks[2 * TransferBatch::MaxNumCached];
  159. };
  160. PerClass PerClassArray[NumClasses] = {};
  161. LocalStats Stats;
  162. SizeClassAllocator *Allocator = nullptr;
  163. ALWAYS_INLINE void initCacheMaybe(PerClass *C) {
  164. if (LIKELY(C->MaxCount))
  165. return;
  166. initCache();
  167. DCHECK_NE(C->MaxCount, 0U);
  168. }
  169. NOINLINE void initCache() {
  170. for (uptr I = 0; I < NumClasses; I++) {
  171. PerClass *P = &PerClassArray[I];
  172. const uptr Size = SizeClassAllocator::getSizeByClassId(I);
  173. P->MaxCount = static_cast<u16>(2 * TransferBatch::getMaxCached(Size));
  174. if (I != BatchClassId) {
  175. P->ClassSize = Size;
  176. } else {
  177. // ClassSize in this struct is only used for malloc/free stats, which
  178. // should only track user allocations, not internal movements.
  179. P->ClassSize = 0;
  180. }
  181. }
  182. }
  183. void destroyBatch(uptr ClassId, void *B) {
  184. if (ClassId != BatchClassId)
  185. deallocate(BatchClassId, B);
  186. }
  187. NOINLINE bool refill(PerClass *C, uptr ClassId) {
  188. initCacheMaybe(C);
  189. TransferBatch *B = Allocator->popBatch(this, ClassId);
  190. if (UNLIKELY(!B))
  191. return false;
  192. DCHECK_GT(B->getCount(), 0);
  193. C->Count = B->getCount();
  194. B->copyToArray(C->Chunks);
  195. B->clear();
  196. destroyBatch(ClassId, B);
  197. return true;
  198. }
  199. NOINLINE void drain(PerClass *C, uptr ClassId) {
  200. const u16 Count = Min(static_cast<u16>(C->MaxCount / 2), C->Count);
  201. Allocator->pushBlocks(this, ClassId, &C->Chunks[0], Count);
  202. // u16 will be promoted to int by arithmetic type conversion.
  203. C->Count = static_cast<u16>(C->Count - Count);
  204. for (u16 I = 0; I < C->Count; I++)
  205. C->Chunks[I] = C->Chunks[I + Count];
  206. }
  207. };
  208. } // namespace scudo
  209. #endif // SCUDO_LOCAL_CACHE_H_