Bug 2055507 - pt 7. Make the chunk cache a C++ class r=glandium
Differential Revision: https://phabricator.services.mozilla.com/D313992
This commit is contained in:
committed by
pbone@mozilla.com
parent
79d627b753
commit
90f5d20710
+147
-164
@@ -34,7 +34,6 @@
|
||||
// Note: MozTaggedAnonymousMmap() could call an LD_PRELOADed mmap
|
||||
// instead of the one defined here; use only MozTagAnonymousMemory().
|
||||
#include "mozilla/TaggedAnonymousMemory.h"
|
||||
#include "mozilla/ThreadSafety.h"
|
||||
|
||||
// For GetGeckoProcessType(), when it's used.
|
||||
#if defined(XP_WIN) && !defined(JS_STANDALONE)
|
||||
@@ -494,36 +493,6 @@ void* pages_mmap_aligned(size_t size, size_t alignment,
|
||||
|
||||
constinit AddressRadixTree<(sizeof(void*) << 3) - LOG2(kChunkSize)> gChunkRTree;
|
||||
|
||||
// Protects chunk-related data structures.
|
||||
static Mutex chunks_mtx;
|
||||
|
||||
// Trees of chunks that were previously allocated (trees differ only in node
|
||||
// ordering). These are used when allocating chunks, in an attempt to re-use
|
||||
// address space. Depending on function, different tree orderings are needed,
|
||||
// which is why there are two trees with the same contents.
|
||||
static RedBlackTree<extent_node_t, ExtentTreeSzTrait> gChunksBySize
|
||||
MOZ_GUARDED_BY(chunks_mtx);
|
||||
static RedBlackTree<extent_node_t, ExtentTreeTrait> gChunksByAddress
|
||||
MOZ_GUARDED_BY(chunks_mtx);
|
||||
|
||||
// The current amount of recycled bytes, updated atomically.
|
||||
Atomic<size_t> gRecycledSize;
|
||||
|
||||
void chunks_init() {
|
||||
// Initialize chunks data.
|
||||
chunks_mtx.Init();
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
// On Windows, calls to VirtualAlloc and VirtualFree must be matched, making it
|
||||
// awkward to recycle allocations of varying sizes. Therefore we only allow
|
||||
// recycling when the size equals the chunksize, unless deallocation is entirely
|
||||
// disabled.
|
||||
# define CAN_RECYCLE(size) ((size) == kChunkSize)
|
||||
#else
|
||||
# define CAN_RECYCLE(size) true
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_DEBUG
|
||||
void chunk_assert_zero(void* aPtr, size_t aSize) {
|
||||
// Only run this expensive check in a vigilant mode.
|
||||
@@ -538,23 +507,150 @@ void chunk_assert_zero(void* aPtr, size_t aSize) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static void chunk_record(void* aChunk, size_t aSize, ChunkType aType) {
|
||||
// Deallocate chunks, possibly recording them for future recycling.
|
||||
// Used for both base allocator chunks and arena chunks already
|
||||
// removed from gChunkRTree.
|
||||
void base_chunk_dealloc(void* aChunk, size_t aSize, ChunkType aType) {
|
||||
MOZ_ASSERT(aChunk);
|
||||
MOZ_ASSERT(GetChunkOffsetForPtr(aChunk) == 0);
|
||||
MOZ_ASSERT(aSize != 0);
|
||||
MOZ_ASSERT((aSize & kChunkSizeMask) == 0);
|
||||
MOZ_ASSERT(!gChunkRTree.Get(aChunk));
|
||||
|
||||
if (gCache.TryRecord(aChunk, aSize, aType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pages_unmap(aChunk, aSize);
|
||||
}
|
||||
|
||||
// Deallocate chunks used for Arena allocations.
|
||||
void arena_chunk_dealloc(chunk_allocator_t* aChunkAllocator, void* aChunk,
|
||||
size_t aSize) {
|
||||
MOZ_ASSERT(aChunk);
|
||||
MOZ_ASSERT(GetChunkOffsetForPtr(aChunk) == 0);
|
||||
MOZ_ASSERT(aSize != 0);
|
||||
MOZ_ASSERT((aSize & kChunkSizeMask) == 0);
|
||||
|
||||
gChunkRTree.Unset(aChunk);
|
||||
|
||||
aChunkAllocator->unmap(aChunk, aSize);
|
||||
}
|
||||
|
||||
// Allocates `size` bytes of system memory aligned for `alignment` for the
|
||||
// base allocator.
|
||||
void* base_chunk_alloc(size_t aSize, size_t aAlignment) {
|
||||
MOZ_ASSERT(aSize != 0);
|
||||
MOZ_ASSERT((aSize & kChunkSizeMask) == 0);
|
||||
MOZ_ASSERT(aAlignment != 0);
|
||||
MOZ_ASSERT((aAlignment & kChunkSizeMask) == 0);
|
||||
|
||||
// Base allocations can't be fulfilled by recycling because of
|
||||
// possible deadlock or infinite recursion.
|
||||
void* ret = pages_mmap_aligned(aSize, aAlignment, ReserveAndCommit);
|
||||
MOZ_ASSERT(GetChunkOffsetForPtr(ret) == 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Allocates `size` bytes of system memory aligned for `alignment` for
|
||||
// arena allocations.
|
||||
void* arena_chunk_alloc(chunk_allocator_t* aChunkAllocator, size_t aSize,
|
||||
size_t aAlignment) {
|
||||
MOZ_ASSERT(aSize != 0);
|
||||
MOZ_ASSERT((aSize & kChunkSizeMask) == 0);
|
||||
MOZ_ASSERT(aAlignment != 0);
|
||||
MOZ_ASSERT((aAlignment & kChunkSizeMask) == 0);
|
||||
|
||||
void* ret = aChunkAllocator->map(aSize, aAlignment);
|
||||
if (ret) {
|
||||
if (!gChunkRTree.Set(ret, ret)) {
|
||||
aChunkAllocator->unmap(ret, aSize);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(GetChunkOffsetForPtr(ret) == 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void* system_pages_map(size_t aSize, size_t aAlignment) {
|
||||
void* ret = gCache.Recycle(aSize, aAlignment);
|
||||
if (!ret) {
|
||||
ret = pages_mmap_aligned(aSize, aAlignment, ReserveAndCommit);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void system_pages_unmap(void* aAddr, size_t aSize) {
|
||||
base_chunk_dealloc(aAddr, aSize, ARENA_CHUNK);
|
||||
}
|
||||
|
||||
chunk_allocator_t gSystemChunkAllocator{
|
||||
.map = system_pages_map,
|
||||
.unmap = system_pages_unmap,
|
||||
.commit = pages_commit,
|
||||
.decommit = pages_decommit,
|
||||
};
|
||||
|
||||
arena_chunk_t::arena_chunk_t(arena_t* aArena)
|
||||
: mArena(aArena), mDirtyRunHint(gChunkHeaderNumPages) {}
|
||||
|
||||
bool arena_chunk_t::IsEmpty() {
|
||||
return (mPageMap[gChunkHeaderNumPages].bits &
|
||||
(~gPageSizeMask | CHUNK_MAP_ALLOCATED)) == gMaxLargeClass;
|
||||
}
|
||||
|
||||
bool ChunkCache::TryRecord(void* aChunk, size_t aSize, ChunkType aType) {
|
||||
if (!CanRecycle(aSize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t recycled_so_far = mRecycledSize;
|
||||
|
||||
// In case some race condition put us above the limit.
|
||||
if (recycled_so_far >= gRecycleLimit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t recycle_remaining = gRecycleLimit - recycled_so_far;
|
||||
size_t to_recycle;
|
||||
if (aSize > recycle_remaining) {
|
||||
#ifndef XP_WIN
|
||||
to_recycle = recycle_remaining;
|
||||
// Drop pages that would overflow the recycle limit
|
||||
pages_trim(aChunk, aSize, 0, to_recycle, ReserveAndCommit);
|
||||
#else
|
||||
// On windows pages_trim unallocates and reallocates the whole
|
||||
// chunk, there's no point doing that during recycling so instead we
|
||||
// fail.
|
||||
pages_unmap(aChunk, aSize);
|
||||
return;
|
||||
#endif
|
||||
} else {
|
||||
to_recycle = aSize;
|
||||
}
|
||||
Record(aChunk, to_recycle, aType);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChunkCache::Record(void* aChunk, size_t aSize, ChunkType aType) {
|
||||
if (aType != ZEROED_CHUNK) {
|
||||
pages_purge(aChunk, aSize);
|
||||
aType = ZEROED_CHUNK;
|
||||
}
|
||||
|
||||
// Allocate a node before acquiring chunks_mtx even though it might not
|
||||
// be needed, otherwise the base allocator may cause a new base chunk to
|
||||
// be allocated, which could cause deadlock if chunks_mtx were already
|
||||
// held.
|
||||
// Allocate a node before acquiring mMutex even though it might not be
|
||||
// needed, otherwise the base allocator may cause a new base chunk to be
|
||||
// allocated, which could cause deadlock if mMutex were already held.
|
||||
UniqueBaseNode xnode(new (fallible) extent_node_t());
|
||||
// Use xprev to implement conditional deferred deallocation of prev.
|
||||
UniqueBaseNode xprev;
|
||||
|
||||
// RAII deallocates xnode and xprev defined above after unlocking
|
||||
// in order to avoid potential dead-locks
|
||||
MutexAutoLock lock(chunks_mtx);
|
||||
MutexAutoLock lock(mMutex);
|
||||
void* addr = (void*)((uintptr_t)aChunk + aSize);
|
||||
extent_node_t* node = gChunksByAddress.SearchOrNext(addr);
|
||||
// Try to coalesce forward.
|
||||
@@ -605,71 +701,24 @@ static void chunk_record(void* aChunk, size_t aSize, ChunkType aType) {
|
||||
xprev.reset(prev);
|
||||
}
|
||||
|
||||
gRecycledSize += aSize;
|
||||
mRecycledSize += aSize;
|
||||
}
|
||||
|
||||
// Deallocate chunks, possibly recording them for future recycling.
|
||||
// Used for both base allocator chunks and arena chunks already
|
||||
// removed from gChunkRTree.
|
||||
void base_chunk_dealloc(void* aChunk, size_t aSize, ChunkType aType) {
|
||||
MOZ_ASSERT(aChunk);
|
||||
MOZ_ASSERT(GetChunkOffsetForPtr(aChunk) == 0);
|
||||
MOZ_ASSERT(aSize != 0);
|
||||
MOZ_ASSERT((aSize & kChunkSizeMask) == 0);
|
||||
MOZ_ASSERT(!gChunkRTree.Get(aChunk));
|
||||
|
||||
if (CAN_RECYCLE(aSize)) {
|
||||
size_t recycled_so_far = gRecycledSize;
|
||||
// In case some race condition put us above the limit.
|
||||
if (recycled_so_far < gRecycleLimit) {
|
||||
size_t recycle_remaining = gRecycleLimit - recycled_so_far;
|
||||
size_t to_recycle;
|
||||
if (aSize > recycle_remaining) {
|
||||
#ifndef XP_WIN
|
||||
to_recycle = recycle_remaining;
|
||||
// Drop pages that would overflow the recycle limit
|
||||
pages_trim(aChunk, aSize, 0, to_recycle, ReserveAndCommit);
|
||||
#else
|
||||
// On windows pages_trim unallocates and reallocates the whole
|
||||
// chunk, there's no point doing that during recycling so instead we
|
||||
// fail.
|
||||
pages_unmap(aChunk, aSize);
|
||||
return;
|
||||
#endif
|
||||
} else {
|
||||
to_recycle = aSize;
|
||||
}
|
||||
chunk_record(aChunk, to_recycle, aType);
|
||||
return;
|
||||
}
|
||||
void* ChunkCache::Recycle(size_t aSize, size_t aAlignment) {
|
||||
if (!CanRecycle(aSize)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
pages_unmap(aChunk, aSize);
|
||||
}
|
||||
|
||||
// Deallocate chunks used for Arena allocations.
|
||||
void arena_chunk_dealloc(chunk_allocator_t* aChunkAllocator, void* aChunk,
|
||||
size_t aSize) {
|
||||
MOZ_ASSERT(aChunk);
|
||||
MOZ_ASSERT(GetChunkOffsetForPtr(aChunk) == 0);
|
||||
MOZ_ASSERT(aSize != 0);
|
||||
MOZ_ASSERT((aSize & kChunkSizeMask) == 0);
|
||||
|
||||
gChunkRTree.Unset(aChunk);
|
||||
|
||||
aChunkAllocator->unmap(aChunk, aSize);
|
||||
}
|
||||
|
||||
static void* chunk_recycle(size_t aSize, size_t aAlignment) {
|
||||
size_t alloc_size = aSize + aAlignment - kChunkSize;
|
||||
// Beware size_t wrap-around.
|
||||
if (alloc_size < aSize) {
|
||||
return nullptr;
|
||||
}
|
||||
chunks_mtx.Lock();
|
||||
|
||||
mMutex.Lock();
|
||||
extent_node_t* node = gChunksBySize.SearchOrNext(alloc_size);
|
||||
if (!node) {
|
||||
chunks_mtx.Unlock();
|
||||
mMutex.Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
size_t leadsize = ALIGNMENT_CEILING((uintptr_t)node->mAddr, aAlignment) -
|
||||
@@ -696,16 +745,16 @@ static void* chunk_recycle(size_t aSize, size_t aAlignment) {
|
||||
// Insert the trailing space as a smaller chunk.
|
||||
if (!node) {
|
||||
// An additional node is required, but BaseAlloc::alloc() may cause a
|
||||
// new base chunk to be allocated. Drop chunks_mtx in order to avoid
|
||||
// new base chunk to be allocated. Drop mMutex in order to avoid
|
||||
// deadlock, and if node allocation fails, deallocate the result
|
||||
// before returning an error.
|
||||
chunks_mtx.Unlock();
|
||||
mMutex.Unlock();
|
||||
node = new (fallible) extent_node_t();
|
||||
if (!node) {
|
||||
base_chunk_dealloc(ret, aSize, ZEROED_CHUNK);
|
||||
return nullptr;
|
||||
}
|
||||
chunks_mtx.Lock();
|
||||
mMutex.Lock();
|
||||
}
|
||||
node->mAddr = (void*)((uintptr_t)(ret) + aSize);
|
||||
node->mSize = trailsize;
|
||||
@@ -715,9 +764,9 @@ static void* chunk_recycle(size_t aSize, size_t aAlignment) {
|
||||
node = nullptr;
|
||||
}
|
||||
|
||||
gRecycledSize -= aSize;
|
||||
mRecycledSize -= aSize;
|
||||
|
||||
chunks_mtx.Unlock();
|
||||
mMutex.Unlock();
|
||||
|
||||
if (node) {
|
||||
delete node;
|
||||
@@ -729,71 +778,5 @@ static void* chunk_recycle(size_t aSize, size_t aAlignment) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Allocates `size` bytes of system memory aligned for `alignment` for the
|
||||
// base allocator.
|
||||
void* base_chunk_alloc(size_t aSize, size_t aAlignment) {
|
||||
MOZ_ASSERT(aSize != 0);
|
||||
MOZ_ASSERT((aSize & kChunkSizeMask) == 0);
|
||||
MOZ_ASSERT(aAlignment != 0);
|
||||
MOZ_ASSERT((aAlignment & kChunkSizeMask) == 0);
|
||||
|
||||
// Base allocations can't be fulfilled by recycling because of
|
||||
// possible deadlock or infinite recursion.
|
||||
void* ret = pages_mmap_aligned(aSize, aAlignment, ReserveAndCommit);
|
||||
MOZ_ASSERT(GetChunkOffsetForPtr(ret) == 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Allocates `size` bytes of system memory aligned for `alignment` for
|
||||
// arena allocations.
|
||||
void* arena_chunk_alloc(chunk_allocator_t* aChunkAllocator, size_t aSize,
|
||||
size_t aAlignment) {
|
||||
MOZ_ASSERT(aSize != 0);
|
||||
MOZ_ASSERT((aSize & kChunkSizeMask) == 0);
|
||||
MOZ_ASSERT(aAlignment != 0);
|
||||
MOZ_ASSERT((aAlignment & kChunkSizeMask) == 0);
|
||||
|
||||
void* ret = aChunkAllocator->map(aSize, aAlignment);
|
||||
if (ret) {
|
||||
if (!gChunkRTree.Set(ret, ret)) {
|
||||
aChunkAllocator->unmap(ret, aSize);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(GetChunkOffsetForPtr(ret) == 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void* system_pages_map(size_t aSize, size_t aAlignment) {
|
||||
void* ret = nullptr;
|
||||
|
||||
if (CAN_RECYCLE(aSize)) {
|
||||
ret = chunk_recycle(aSize, aAlignment);
|
||||
}
|
||||
if (!ret) {
|
||||
ret = pages_mmap_aligned(aSize, aAlignment, ReserveAndCommit);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void system_pages_unmap(void* aAddr, size_t aSize) {
|
||||
base_chunk_dealloc(aAddr, aSize, ARENA_CHUNK);
|
||||
}
|
||||
|
||||
chunk_allocator_t gSystemChunkAllocator{
|
||||
.map = system_pages_map,
|
||||
.unmap = system_pages_unmap,
|
||||
.commit = pages_commit,
|
||||
.decommit = pages_decommit,
|
||||
};
|
||||
|
||||
arena_chunk_t::arena_chunk_t(arena_t* aArena)
|
||||
: mArena(aArena), mDirtyRunHint(gChunkHeaderNumPages) {}
|
||||
|
||||
bool arena_chunk_t::IsEmpty() {
|
||||
return (mPageMap[gChunkHeaderNumPages].bits &
|
||||
(~gPageSizeMask | CHUNK_MAP_ALLOCATED)) == gMaxLargeClass;
|
||||
}
|
||||
// The global chunk cache.
|
||||
ChunkCache gCache;
|
||||
|
||||
+50
-11
@@ -6,9 +6,11 @@
|
||||
#define CHUNK_H
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/ThreadSafety.h"
|
||||
|
||||
#include "mozjemalloc_types.h"
|
||||
|
||||
#include "Extent.h"
|
||||
#include "RadixTree.h"
|
||||
|
||||
#include "mozilla/DoublyLinkedList.h"
|
||||
@@ -18,13 +20,7 @@
|
||||
|
||||
struct arena_t;
|
||||
|
||||
enum ChunkType {
|
||||
UNKNOWN_CHUNK,
|
||||
ZEROED_CHUNK, // chunk only contains zeroes.
|
||||
ARENA_CHUNK, // used to back arena runs created by arena_t::AllocRun.
|
||||
HUGE_CHUNK, // used to back huge allocations (e.g. arena_t::MallocHuge).
|
||||
RECYCLED_CHUNK, // chunk has been stored for future use by chunk_recycle.
|
||||
};
|
||||
enum ChunkType;
|
||||
|
||||
// Each element of the chunk map corresponds to one page within the chunk.
|
||||
struct arena_chunk_map_t {
|
||||
@@ -200,8 +196,6 @@ struct DirtyChunkListTrait {
|
||||
|
||||
void pages_decommit(void* aAddr, size_t aSize);
|
||||
|
||||
void chunks_init();
|
||||
|
||||
void* base_chunk_alloc(size_t aSize, size_t aAlignment);
|
||||
|
||||
void base_chunk_dealloc(void* aChunk, size_t aSize, ChunkType aType);
|
||||
@@ -215,8 +209,6 @@ void arena_chunk_dealloc(chunk_allocator_t* aChunkAllocator, void* aChunk,
|
||||
void chunk_assert_zero(void* aPtr, size_t aSize);
|
||||
#endif
|
||||
|
||||
extern mozilla::Atomic<size_t> gRecycledSize;
|
||||
|
||||
extern AddressRadixTree<(sizeof(void*) << 3) - LOG2(kChunkSize)> gChunkRTree;
|
||||
|
||||
// Default chunk allocator for arena's that uses pages from anywhere in the
|
||||
@@ -238,4 +230,51 @@ void* pages_mmap_aligned(size_t size, size_t alignment,
|
||||
|
||||
void pages_unmap(void* aAddr, size_t aSize);
|
||||
|
||||
class ChunkCache {
|
||||
private:
|
||||
Mutex mMutex;
|
||||
|
||||
// Trees of chunks that were previously allocated (trees differ only in node
|
||||
// ordering). These are used when allocating chunks, in an attempt to re-use
|
||||
// address space. Depending on function, different tree orderings are needed,
|
||||
// which is why there are two trees with the same contents.
|
||||
RedBlackTree<extent_node_t, ExtentTreeSzTrait> gChunksBySize
|
||||
MOZ_GUARDED_BY(mMutex);
|
||||
RedBlackTree<extent_node_t, ExtentTreeTrait> gChunksByAddress
|
||||
MOZ_GUARDED_BY(mMutex);
|
||||
|
||||
// The current amount of recycled bytes, updated atomically.
|
||||
mozilla::Atomic<size_t> mRecycledSize;
|
||||
|
||||
public:
|
||||
constexpr ChunkCache() = default;
|
||||
|
||||
void Init() { mMutex.Init(); }
|
||||
|
||||
static constexpr bool CanRecycle(size_t aSize) {
|
||||
#ifdef XP_WIN
|
||||
// On Windows, calls to VirtualAlloc and VirtualFree must be matched, making
|
||||
// it awkward to recycle allocations of varying sizes. Therefore we only
|
||||
// allow recycling when the size equals the chunksize, unless deallocation
|
||||
// is entirely disabled.
|
||||
return aSize == kChunkSize;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Try to put this chunk in the cache, false if the cache is full.
|
||||
bool TryRecord(void* aChunk, size_t aSize, ChunkType aType);
|
||||
|
||||
private:
|
||||
// Put this chunk in the cache.
|
||||
void Record(void* aChunk, size_t aSize, ChunkType aType);
|
||||
|
||||
public:
|
||||
// Retrive a chunk from the cache.
|
||||
void* Recycle(size_t aSize, size_t aAlignment);
|
||||
};
|
||||
|
||||
extern ChunkCache gCache;
|
||||
|
||||
#endif /* ! CHUNK_H */
|
||||
|
||||
@@ -17,7 +17,13 @@
|
||||
|
||||
struct arena_t;
|
||||
|
||||
enum ChunkType;
|
||||
enum ChunkType {
|
||||
UNKNOWN_CHUNK,
|
||||
ZEROED_CHUNK, // chunk only contains zeroes.
|
||||
ARENA_CHUNK, // used to back arena runs created by arena_t::AllocRun.
|
||||
HUGE_CHUNK, // used to back huge allocations (e.g. arena_t::MallocHuge).
|
||||
RECYCLED_CHUNK, // chunk has been stored for future use by chunk_recycle.
|
||||
};
|
||||
|
||||
// Tree of extents.
|
||||
struct extent_node_t : public BaseAllocClass {
|
||||
|
||||
@@ -3447,9 +3447,8 @@ static bool malloc_init_hard() {
|
||||
#ifndef MALLOC_STATIC_PAGESIZE
|
||||
DefineGlobals();
|
||||
#endif
|
||||
gRecycledSize = 0;
|
||||
|
||||
chunks_init();
|
||||
gCache.Init();
|
||||
huge_init();
|
||||
sBaseAlloc.Init();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user