local_cache.h 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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 "report.h"
  12. #include "stats.h"
  13. namespace scudo {
  14. template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache {
  15. typedef typename SizeClassAllocator::SizeClassMap SizeClassMap;
  16. typedef typename SizeClassAllocator::CompactPtrT CompactPtrT;
  17. struct TransferBatch {
  18. static const u32 MaxNumCached = SizeClassMap::MaxNumCachedHint;
  19. void setFromArray(CompactPtrT *Array, u32 N) {
  20. DCHECK_LE(N, MaxNumCached);
  21. Count = N;
  22. memcpy(Batch, Array, sizeof(Batch[0]) * Count);
  23. }
  24. void clear() { Count = 0; }
  25. void add(CompactPtrT P) {
  26. DCHECK_LT(Count, MaxNumCached);
  27. Batch[Count++] = P;
  28. }
  29. void copyToArray(CompactPtrT *Array) const {
  30. memcpy(Array, Batch, sizeof(Batch[0]) * Count);
  31. }
  32. u32 getCount() const { return Count; }
  33. CompactPtrT get(u32 I) const {
  34. DCHECK_LE(I, Count);
  35. return Batch[I];
  36. }
  37. static u32 getMaxCached(uptr Size) {
  38. return Min(MaxNumCached, SizeClassMap::getMaxCachedHint(Size));
  39. }
  40. TransferBatch *Next;
  41. private:
  42. u32 Count;
  43. CompactPtrT Batch[MaxNumCached];
  44. };
  45. void init(GlobalStats *S, SizeClassAllocator *A) {
  46. DCHECK(isEmpty());
  47. Stats.init();
  48. if (LIKELY(S))
  49. S->link(&Stats);
  50. Allocator = A;
  51. }
  52. void destroy(GlobalStats *S) {
  53. drain();
  54. if (LIKELY(S))
  55. S->unlink(&Stats);
  56. }
  57. void *allocate(uptr ClassId) {
  58. DCHECK_LT(ClassId, NumClasses);
  59. PerClass *C = &PerClassArray[ClassId];
  60. if (C->Count == 0) {
  61. if (UNLIKELY(!refill(C, ClassId)))
  62. return nullptr;
  63. DCHECK_GT(C->Count, 0);
  64. }
  65. // We read ClassSize first before accessing Chunks because it's adjacent to
  66. // Count, while Chunks might be further off (depending on Count). That keeps
  67. // the memory accesses in close quarters.
  68. const uptr ClassSize = C->ClassSize;
  69. CompactPtrT CompactP = C->Chunks[--C->Count];
  70. Stats.add(StatAllocated, ClassSize);
  71. Stats.sub(StatFree, ClassSize);
  72. return Allocator->decompactPtr(ClassId, CompactP);
  73. }
  74. void deallocate(uptr ClassId, void *P) {
  75. CHECK_LT(ClassId, NumClasses);
  76. PerClass *C = &PerClassArray[ClassId];
  77. // We still have to initialize the cache in the event that the first heap
  78. // operation in a thread is a deallocation.
  79. initCacheMaybe(C);
  80. if (C->Count == C->MaxCount)
  81. drain(C, ClassId);
  82. // See comment in allocate() about memory accesses.
  83. const uptr ClassSize = C->ClassSize;
  84. C->Chunks[C->Count++] =
  85. Allocator->compactPtr(ClassId, reinterpret_cast<uptr>(P));
  86. Stats.sub(StatAllocated, ClassSize);
  87. Stats.add(StatFree, ClassSize);
  88. }
  89. bool isEmpty() const {
  90. for (uptr I = 0; I < NumClasses; ++I)
  91. if (PerClassArray[I].Count)
  92. return false;
  93. return true;
  94. }
  95. void drain() {
  96. // Drain BatchClassId last as createBatch can refill it.
  97. for (uptr I = 0; I < NumClasses; ++I) {
  98. if (I == BatchClassId)
  99. continue;
  100. while (PerClassArray[I].Count > 0)
  101. drain(&PerClassArray[I], I);
  102. }
  103. while (PerClassArray[BatchClassId].Count > 0)
  104. drain(&PerClassArray[BatchClassId], BatchClassId);
  105. DCHECK(isEmpty());
  106. }
  107. TransferBatch *createBatch(uptr ClassId, void *B) {
  108. if (ClassId != BatchClassId)
  109. B = allocate(BatchClassId);
  110. return reinterpret_cast<TransferBatch *>(B);
  111. }
  112. LocalStats &getStats() { return Stats; }
  113. private:
  114. static const uptr NumClasses = SizeClassMap::NumClasses;
  115. static const uptr BatchClassId = SizeClassMap::BatchClassId;
  116. struct PerClass {
  117. u32 Count;
  118. u32 MaxCount;
  119. // Note: ClassSize is zero for the transfer batch.
  120. uptr ClassSize;
  121. CompactPtrT Chunks[2 * TransferBatch::MaxNumCached];
  122. };
  123. PerClass PerClassArray[NumClasses] = {};
  124. LocalStats Stats;
  125. SizeClassAllocator *Allocator = nullptr;
  126. ALWAYS_INLINE void initCacheMaybe(PerClass *C) {
  127. if (LIKELY(C->MaxCount))
  128. return;
  129. initCache();
  130. DCHECK_NE(C->MaxCount, 0U);
  131. }
  132. NOINLINE void initCache() {
  133. for (uptr I = 0; I < NumClasses; I++) {
  134. PerClass *P = &PerClassArray[I];
  135. const uptr Size = SizeClassAllocator::getSizeByClassId(I);
  136. P->MaxCount = 2 * TransferBatch::getMaxCached(Size);
  137. if (I != BatchClassId) {
  138. P->ClassSize = Size;
  139. } else {
  140. // ClassSize in this struct is only used for malloc/free stats, which
  141. // should only track user allocations, not internal movements.
  142. P->ClassSize = 0;
  143. }
  144. }
  145. }
  146. void destroyBatch(uptr ClassId, void *B) {
  147. if (ClassId != BatchClassId)
  148. deallocate(BatchClassId, B);
  149. }
  150. NOINLINE bool refill(PerClass *C, uptr ClassId) {
  151. initCacheMaybe(C);
  152. TransferBatch *B = Allocator->popBatch(this, ClassId);
  153. if (UNLIKELY(!B))
  154. return false;
  155. DCHECK_GT(B->getCount(), 0);
  156. C->Count = B->getCount();
  157. B->copyToArray(C->Chunks);
  158. B->clear();
  159. destroyBatch(ClassId, B);
  160. return true;
  161. }
  162. NOINLINE void drain(PerClass *C, uptr ClassId) {
  163. const u32 Count = Min(C->MaxCount / 2, C->Count);
  164. TransferBatch *B =
  165. createBatch(ClassId, Allocator->decompactPtr(ClassId, C->Chunks[0]));
  166. if (UNLIKELY(!B))
  167. reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId));
  168. B->setFromArray(&C->Chunks[0], Count);
  169. C->Count -= Count;
  170. for (uptr I = 0; I < C->Count; I++)
  171. C->Chunks[I] = C->Chunks[I + Count];
  172. Allocator->pushBatch(ClassId, B);
  173. }
  174. };
  175. } // namespace scudo
  176. #endif // SCUDO_LOCAL_CACHE_H_