initCacheMaybe() will init all the size class arrays at once and it doesn't have much work to do even if it supports partial initialization. This avoids the call to initCacheMaybe in each allocate()/deallocate().
190 lines
6.0 KiB
C++
190 lines
6.0 KiB
C++
//===-- local_cache.h -------------------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SCUDO_LOCAL_CACHE_H_
|
|
#define SCUDO_LOCAL_CACHE_H_
|
|
|
|
#include "internal_defs.h"
|
|
#include "list.h"
|
|
#include "platform.h"
|
|
#include "report.h"
|
|
#include "stats.h"
|
|
#include "string_utils.h"
|
|
|
|
namespace scudo {
|
|
|
|
template <class SizeClassAllocator> struct SizeClassAllocatorLocalCache {
|
|
typedef typename SizeClassAllocator::SizeClassMap SizeClassMap;
|
|
typedef typename SizeClassAllocator::CompactPtrT CompactPtrT;
|
|
|
|
void init(GlobalStats *S, SizeClassAllocator *A) {
|
|
DCHECK(isEmpty());
|
|
Stats.init();
|
|
if (LIKELY(S))
|
|
S->link(&Stats);
|
|
Allocator = A;
|
|
initCache();
|
|
}
|
|
|
|
void destroy(GlobalStats *S) {
|
|
drain();
|
|
if (LIKELY(S))
|
|
S->unlink(&Stats);
|
|
}
|
|
|
|
void *allocate(uptr ClassId) {
|
|
DCHECK_LT(ClassId, NumClasses);
|
|
PerClass *C = &PerClassArray[ClassId];
|
|
if (C->Count == 0) {
|
|
// Refill half of the number of max cached.
|
|
DCHECK_GT(C->MaxCount / 2, 0U);
|
|
if (UNLIKELY(!refill(C, ClassId, C->MaxCount / 2)))
|
|
return nullptr;
|
|
DCHECK_GT(C->Count, 0);
|
|
}
|
|
// We read ClassSize first before accessing Chunks because it's adjacent to
|
|
// Count, while Chunks might be further off (depending on Count). That keeps
|
|
// the memory accesses in close quarters.
|
|
const uptr ClassSize = C->ClassSize;
|
|
CompactPtrT CompactP = C->Chunks[--C->Count];
|
|
Stats.add(StatAllocated, ClassSize);
|
|
Stats.sub(StatFree, ClassSize);
|
|
return Allocator->decompactPtr(ClassId, CompactP);
|
|
}
|
|
|
|
bool deallocate(uptr ClassId, void *P) {
|
|
CHECK_LT(ClassId, NumClasses);
|
|
PerClass *C = &PerClassArray[ClassId];
|
|
|
|
// If the cache is full, drain half of blocks back to the main allocator.
|
|
const bool NeedToDrainCache = C->Count == C->MaxCount;
|
|
if (NeedToDrainCache)
|
|
drain(C, ClassId);
|
|
// See comment in allocate() about memory accesses.
|
|
const uptr ClassSize = C->ClassSize;
|
|
C->Chunks[C->Count++] =
|
|
Allocator->compactPtr(ClassId, reinterpret_cast<uptr>(P));
|
|
Stats.sub(StatAllocated, ClassSize);
|
|
Stats.add(StatFree, ClassSize);
|
|
|
|
return NeedToDrainCache;
|
|
}
|
|
|
|
bool isEmpty() const {
|
|
for (uptr I = 0; I < NumClasses; ++I)
|
|
if (PerClassArray[I].Count)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void drain() {
|
|
// Drain BatchClassId last as it may be needed while draining normal blocks.
|
|
for (uptr I = 0; I < NumClasses; ++I) {
|
|
if (I == BatchClassId)
|
|
continue;
|
|
while (PerClassArray[I].Count > 0)
|
|
drain(&PerClassArray[I], I);
|
|
}
|
|
while (PerClassArray[BatchClassId].Count > 0)
|
|
drain(&PerClassArray[BatchClassId], BatchClassId);
|
|
DCHECK(isEmpty());
|
|
}
|
|
|
|
void *getBatchClassBlock() {
|
|
void *B = allocate(BatchClassId);
|
|
if (UNLIKELY(!B))
|
|
reportOutOfMemory(SizeClassAllocator::getSizeByClassId(BatchClassId));
|
|
return B;
|
|
}
|
|
|
|
LocalStats &getStats() { return Stats; }
|
|
|
|
void getStats(ScopedString *Str) {
|
|
bool EmptyCache = true;
|
|
for (uptr I = 0; I < NumClasses; ++I) {
|
|
if (PerClassArray[I].Count == 0)
|
|
continue;
|
|
|
|
EmptyCache = false;
|
|
// The size of BatchClass is set to 0 intentionally. See the comment in
|
|
// initCache() for more details.
|
|
const uptr ClassSize = I == BatchClassId
|
|
? SizeClassAllocator::getSizeByClassId(I)
|
|
: PerClassArray[I].ClassSize;
|
|
// Note that the string utils don't support printing u16 thus we cast it
|
|
// to a common use type uptr.
|
|
Str->append(" %02zu (%6zu): cached: %4zu max: %4zu\n", I, ClassSize,
|
|
static_cast<uptr>(PerClassArray[I].Count),
|
|
static_cast<uptr>(PerClassArray[I].MaxCount));
|
|
}
|
|
|
|
if (EmptyCache)
|
|
Str->append(" No block is cached.\n");
|
|
}
|
|
|
|
static u16 getMaxCached(uptr Size) {
|
|
return Min(SizeClassMap::MaxNumCachedHint,
|
|
SizeClassMap::getMaxCachedHint(Size));
|
|
}
|
|
|
|
private:
|
|
static const uptr NumClasses = SizeClassMap::NumClasses;
|
|
static const uptr BatchClassId = SizeClassMap::BatchClassId;
|
|
struct alignas(SCUDO_CACHE_LINE_SIZE) PerClass {
|
|
u16 Count;
|
|
u16 MaxCount;
|
|
// Note: ClassSize is zero for the transfer batch.
|
|
uptr ClassSize;
|
|
CompactPtrT Chunks[2 * SizeClassMap::MaxNumCachedHint];
|
|
};
|
|
PerClass PerClassArray[NumClasses] = {};
|
|
LocalStats Stats;
|
|
SizeClassAllocator *Allocator = nullptr;
|
|
|
|
NOINLINE void initCache() {
|
|
for (uptr I = 0; I < NumClasses; I++) {
|
|
PerClass *P = &PerClassArray[I];
|
|
const uptr Size = SizeClassAllocator::getSizeByClassId(I);
|
|
P->MaxCount = static_cast<u16>(2 * getMaxCached(Size));
|
|
if (I != BatchClassId) {
|
|
P->ClassSize = Size;
|
|
} else {
|
|
// ClassSize in this struct is only used for malloc/free stats, which
|
|
// should only track user allocations, not internal movements.
|
|
P->ClassSize = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void destroyBatch(uptr ClassId, void *B) {
|
|
if (ClassId != BatchClassId)
|
|
deallocate(BatchClassId, B);
|
|
}
|
|
|
|
NOINLINE bool refill(PerClass *C, uptr ClassId, u16 MaxRefill) {
|
|
const u16 NumBlocksRefilled =
|
|
Allocator->popBlocks(this, ClassId, C->Chunks, MaxRefill);
|
|
DCHECK_LE(NumBlocksRefilled, MaxRefill);
|
|
C->Count = static_cast<u16>(C->Count + NumBlocksRefilled);
|
|
return NumBlocksRefilled != 0;
|
|
}
|
|
|
|
NOINLINE void drain(PerClass *C, uptr ClassId) {
|
|
const u16 Count = Min(static_cast<u16>(C->MaxCount / 2), C->Count);
|
|
Allocator->pushBlocks(this, ClassId, &C->Chunks[0], Count);
|
|
// u16 will be promoted to int by arithmetic type conversion.
|
|
C->Count = static_cast<u16>(C->Count - Count);
|
|
for (u16 I = 0; I < C->Count; I++)
|
|
C->Chunks[I] = C->Chunks[I + Count];
|
|
}
|
|
};
|
|
|
|
} // namespace scudo
|
|
|
|
#endif // SCUDO_LOCAL_CACHE_H_
|