Bug 808467 - Rewrite the cycle collector's memory reporter. r=mccr8.

--HG--
extra : rebase_source : 9944d59772654a1df8db3227f1a8b18ae3dcfeda
This commit is contained in:
Nicholas Nethercote 2012-11-06 17:38:29 -08:00
Родитель 9d0bbc706e
Коммит ab8ff34585
1 изменённых файлов: 185 добавлений и 77 удалений

Просмотреть файл

@ -6,7 +6,7 @@
//
// This file implements a garbage-cycle collector based on the paper
//
//
// Concurrent Cycle Collection in Reference Counted Systems
// Bacon & Rajan (2001), ECOOP 2001 / Springer LNCS vol 2072
//
@ -320,7 +320,6 @@ public:
{
mSentinelAndBlocks[0].block = nullptr;
mSentinelAndBlocks[1].block = nullptr;
mNumBlocks = 0;
}
~EdgePool()
@ -336,9 +335,6 @@ public:
while (b) {
Block *next = b->Next();
delete b;
NS_ASSERTION(mNumBlocks > 0,
"Expected EdgePool mNumBlocks to be positive.");
mNumBlocks--;
b = next;
}
@ -362,20 +358,17 @@ private:
mPointers[BlockSize - 2].block = nullptr; // sentinel
mPointers[BlockSize - 1].block = nullptr; // next block pointer
}
Block*& Next()
{ return mPointers[BlockSize - 1].block; }
PtrInfoOrBlock* Start()
{ return &mPointers[0]; }
PtrInfoOrBlock* End()
{ return &mPointers[BlockSize - 2]; }
Block*& Next() { return mPointers[BlockSize - 1].block; }
PtrInfoOrBlock* Start() { return &mPointers[0]; }
PtrInfoOrBlock* End() { return &mPointers[BlockSize - 2]; }
};
// Store the null sentinel so that we can have valid iterators
// before adding any edges and without adding any blocks.
PtrInfoOrBlock mSentinelAndBlocks[2];
uint32_t mNumBlocks;
Block*& Blocks() { return mSentinelAndBlocks[1].block; }
Block*& Blocks() { return mSentinelAndBlocks[1].block; }
Block* Blocks() const { return mSentinelAndBlocks[1].block; }
public:
class Iterator
@ -419,8 +412,7 @@ public:
Builder(EdgePool &aPool)
: mCurrent(&aPool.mSentinelAndBlocks[0]),
mBlockEnd(&aPool.mSentinelAndBlocks[0]),
mNextBlockPtr(&aPool.Blocks()),
mNumBlocks(aPool.mNumBlocks)
mNextBlockPtr(&aPool.Blocks())
{
}
@ -438,7 +430,6 @@ public:
mCurrent = b->Start();
mBlockEnd = b->End();
mNextBlockPtr = &b->Next();
mNumBlocks++;
}
(mCurrent++)->ptrInfo = aEdge;
}
@ -446,13 +437,17 @@ public:
// mBlockEnd points to space for null sentinel
PtrInfoOrBlock *mCurrent, *mBlockEnd;
Block **mNextBlockPtr;
uint32_t &mNumBlocks;
};
size_t BlocksSize() const {
return sizeof(Block) * mNumBlocks;
size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const {
size_t n = 0;
Block *b = Blocks();
while (b) {
n += aMallocSizeOf(b);
b = b->Next();
}
return n;
}
};
enum NodeColor { black, white, grey };
@ -537,7 +532,7 @@ private:
// We create and destroy Block using NS_Alloc/NS_Free rather
// than new and delete to avoid calling its constructor and
// destructor.
Block() { NS_NOTREACHED("should never be called"); }
Block() { NS_NOTREACHED("should never be called"); }
~Block() { NS_NOTREACHED("should never be called"); }
Block* mNext;
@ -547,8 +542,7 @@ private:
public:
NodePool()
: mBlocks(nullptr),
mLast(nullptr),
mNumBlocks(0)
mLast(nullptr)
{
}
@ -571,9 +565,6 @@ public:
while (b) {
Block *n = b->mNext;
NS_Free(b);
NS_ASSERTION(mNumBlocks > 0,
"Expected NodePool mNumBlocks to be positive.");
mNumBlocks--;
b = n;
}
@ -588,8 +579,7 @@ public:
Builder(NodePool& aPool)
: mNextBlock(&aPool.mBlocks),
mNext(aPool.mLast),
mBlockEnd(nullptr),
mNumBlocks(aPool.mNumBlocks)
mBlockEnd(nullptr)
{
NS_ASSERTION(aPool.mBlocks == nullptr && aPool.mLast == nullptr,
"pool not empty");
@ -605,7 +595,6 @@ public:
mBlockEnd = block->mEntries + BlockSize;
block->mNext = nullptr;
mNextBlock = &block->mNext;
mNumBlocks++;
}
return new (mNext++) PtrInfo(aPointer, aParticipant);
}
@ -613,7 +602,6 @@ public:
Block **mNextBlock;
PtrInfo *&mNext;
PtrInfo *mBlockEnd;
uint32_t &mNumBlocks;
};
class Enumerator;
@ -657,14 +645,21 @@ public:
PtrInfo *mNext, *mBlockEnd, *&mLast;
};
size_t BlocksSize() const {
return sizeof(Block) * mNumBlocks;
size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const {
// We don't measure the things pointed to by mEntries[] because those
// pointers are non-owning.
size_t n = 0;
Block *b = mBlocks;
while (b) {
n += aMallocSizeOf(b);
b = b->mNext;
}
return n;
}
private:
Block *mBlocks;
PtrInfo *mLast;
uint32_t mNumBlocks;
};
@ -688,13 +683,17 @@ struct GCGraph
GCGraph() : mRootCount(0) {
}
~GCGraph() {
~GCGraph() {
}
size_t BlocksSize() const {
return mNodes.BlocksSize() + mEdges.BlocksSize();
}
void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf,
size_t *aNodesSize, size_t *aEdgesSize) const {
*aNodesSize = mNodes.SizeOfExcludingThis(aMallocSizeOf);
*aEdgesSize = mEdges.SizeOfExcludingThis(aMallocSizeOf);
// These fields are deliberately not measured:
// - mWeakMaps entries, because the pointers are non-owning
}
};
// XXX Would be nice to have an nsHashSet<KeyType> API that has
@ -736,17 +735,30 @@ struct nsPurpleBuffer
private:
struct Block {
Block *mNext;
// Try to match the size of a jemalloc bucket.
nsPurpleBufferEntry mEntries[1360];
// Try to match the size of a jemalloc bucket, to minimize slop bytes.
// - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries
// is 16,380 bytes, which leaves 4 bytes for mNext.
// - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries
// is 32,544 bytes, which leaves 8 bytes for mNext.
nsPurpleBufferEntry mEntries[1365];
Block() : mNext(nullptr) {}
Block() : mNext(nullptr) {
#ifndef DEBUG_CC
// Ensure Block is the right size (see above).
MOZ_STATIC_ASSERT(
sizeof(Block) == 16384 || // 32-bit
sizeof(Block) == 32768, // 64-bit
"ill-sized nsPurpleBuffer::Block"
);
#endif
}
void StaticAsserts();
};
public:
// This class wraps a linked list of the elements in the purple
// buffer.
nsCycleCollectorParams &mParams;
uint32_t mNumBlocksAlloced;
uint32_t mCount;
Block mFirstBlock;
nsPurpleBufferEntry *mFreeList;
@ -755,10 +767,10 @@ public:
PointerSet mNormalObjects; // duplicates our blocks
nsCycleCollectorStats &mStats;
#endif
#ifdef DEBUG_CC
nsPurpleBuffer(nsCycleCollectorParams &params,
nsCycleCollectorStats &stats)
nsCycleCollectorStats &stats)
: mParams(params),
mStats(stats)
{
@ -766,7 +778,7 @@ public:
mNormalObjects.Init();
}
#else
nsPurpleBuffer(nsCycleCollectorParams &params)
nsPurpleBuffer(nsCycleCollectorParams &params)
: mParams(params)
{
InitBlocks();
@ -780,7 +792,6 @@ public:
void InitBlocks()
{
mNumBlocksAlloced = 0;
mCount = 0;
mFreeList = nullptr;
StartBlock(&mFirstBlock);
@ -805,16 +816,13 @@ public:
{
if (mCount > 0)
UnmarkRemainingPurple(&mFirstBlock);
Block *b = mFirstBlock.mNext;
Block *b = mFirstBlock.mNext;
while (b) {
if (mCount > 0)
UnmarkRemainingPurple(b);
Block *next = b->mNext;
delete b;
b = next;
NS_ASSERTION(mNumBlocksAlloced > 0,
"Expected positive mNumBlocksAlloced.");
mNumBlocksAlloced--;
}
mFirstBlock.mNext = nullptr;
}
@ -865,7 +873,6 @@ public:
if (!b) {
return nullptr;
}
mNumBlocksAlloced++;
StartBlock(b);
// Add the new block as the second block in the list.
@ -916,11 +923,28 @@ public:
return mCount;
}
size_t BlocksSize() const
size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const
{
return sizeof(Block) * mNumBlocksAlloced;
}
size_t n = 0;
// Don't measure mFirstBlock because it's within |this|.
const Block *block = mFirstBlock.mNext;
while (block) {
n += aMallocSizeOf(block);
block = block->mNext;
}
// These fields are deliberately not measured:
// - mParams: because it only contains scalars.
// - mFreeList: because it points into the purple buffer, which is
// within mFirstBlock and thus within |this|.
// - mNormalObjects, mStats: because they're DEBUG_CC-only.
//
// We also don't measure the things pointed to by mEntries[] because
// those pointers are non-owning.
return n;
}
};
static bool
@ -977,8 +1001,6 @@ nsPurpleBuffer::SelectPointers(GCGraphBuilder &aBuilder)
}
}
////////////////////////////////////////////////////////////////////////
// Top level structure for the cycle collector.
////////////////////////////////////////////////////////////////////////
@ -1054,6 +1076,13 @@ struct nsCycleCollector
mGraph.mRootCount = 0;
}
void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf,
size_t *aObjectSize,
size_t *aGraphNodesSize,
size_t *aGraphEdgesSize,
size_t *aWhiteNodeSize,
size_t *aPurpleBufferSize) const;
#ifdef DEBUG_CC
nsCycleCollectorStats mStats;
FILE *mPtrLog;
@ -1098,6 +1127,7 @@ public:
static nsCycleCollector *sCollector = nullptr;
static nsIMemoryMultiReporter *sCollectorReporter = nullptr;
////////////////////////////////////////////////////////////////////////
@ -2963,30 +2993,103 @@ nsCycleCollector::WasFreed(nsISupports *n)
// Memory reporter
////////////////////////
static int64_t
GetCycleCollectorSize()
NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(CycleCollectorMallocSizeOf,
"cycle-collector")
void
nsCycleCollector::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf,
size_t *aObjectSize,
size_t *aGraphNodesSize,
size_t *aGraphEdgesSize,
size_t *aWhiteNodeSize,
size_t *aPurpleBufferSize) const
{
if (!sCollector)
return 0;
int64_t size = sizeof(nsCycleCollector) +
sCollector->mPurpleBuf.BlocksSize() +
sCollector->mGraph.BlocksSize();
if (sCollector->mWhiteNodes)
size += sCollector->mWhiteNodes->Capacity() * sizeof(PtrInfo*);
return size;
*aObjectSize = aMallocSizeOf(this);
mGraph.SizeOfExcludingThis(aMallocSizeOf, aGraphNodesSize, aGraphEdgesSize);
// No need to measure what the entries point to; the pointers are
// non-owning.
*aWhiteNodeSize = mWhiteNodes
? mWhiteNodes->SizeOfIncludingThis(aMallocSizeOf)
: 0;
*aPurpleBufferSize = mPurpleBuf.SizeOfExcludingThis(aMallocSizeOf);
// These fields are deliberately not measured:
// - mResults: because it's tiny and only contains scalars.
// - mJSRuntime: because it's non-owning and measured by JS reporters.
// - mParams: because it only contains scalars.
// - mStats, mPtrLog, mExpectedGarbage: because they're DEBUG_CC-only.
}
NS_MEMORY_REPORTER_IMPLEMENT(CycleCollector,
"explicit/cycle-collector",
KIND_HEAP,
UNITS_BYTES,
GetCycleCollectorSize,
"Memory used by the cycle collector. This "
"includes the cycle collector structure, the "
"purple buffer, the graph, and the white nodes. "
"The latter two are expected to be empty when the "
"cycle collector is idle.")
class CycleCollectorMultiReporter MOZ_FINAL : public nsIMemoryMultiReporter
{
public:
NS_DECL_ISUPPORTS
NS_IMETHOD GetName(nsACString &name)
{
name.AssignLiteral("cycle-collector");
return NS_OK;
}
NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *aCb,
nsISupports *aClosure)
{
if (!sCollector)
return NS_OK;
size_t objectSize, graphNodesSize, graphEdgesSize, whiteNodesSize,
purpleBufferSize;
sCollector->SizeOfIncludingThis(CycleCollectorMallocSizeOf,
&objectSize, &graphNodesSize,
&graphEdgesSize, &whiteNodesSize,
&purpleBufferSize);
#define REPORT(_path, _amount, _desc) \
do { \
size_t amount = _amount; /* evaluate |_amount| just once */ \
if (amount > 0) { \
nsresult rv; \
rv = aCb->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
nsIMemoryReporter::KIND_HEAP, \
nsIMemoryReporter::UNITS_BYTES, _amount, \
NS_LITERAL_CSTRING(_desc), aClosure); \
NS_ENSURE_SUCCESS(rv, rv); \
} \
} while (0)
REPORT("explicit/cycle-collector/collector-object", objectSize,
"Memory used for the cycle collector object itself.");
REPORT("explicit/cycle-collector/graph-nodes", graphNodesSize,
"Memory used for the nodes of the cycle collector's graph. "
"This should be zero when the collector is idle.");
REPORT("explicit/cycle-collector/graph-edges", graphEdgesSize,
"Memory used for the edges of the cycle collector's graph. "
"This should be zero when the collector is idle.");
REPORT("explicit/cycle-collector/white-nodes", whiteNodesSize,
"Memory used for the cycle collector's white nodes array. "
"This should be zero when the collector is idle.");
REPORT("explicit/cycle-collector/purple-buffer", purpleBufferSize,
"Memory used for the cycle collector's purple buffer.");
return NS_OK;
}
NS_IMETHOD GetExplicitNonHeap(int64_t *n)
{
// This reporter does neither "explicit" nor NONHEAP measurements.
*n = 0;
return NS_OK;
}
};
NS_IMPL_ISUPPORTS1(CycleCollectorMultiReporter, nsIMemoryMultiReporter)
////////////////////////////////////////////////////////////////////////
// Module public API (exported in nsCycleCollector.h)
@ -3001,15 +3104,20 @@ nsCycleCollector_registerJSRuntime(nsCycleCollectionJSRuntime *rt)
sCollector->RegisterJSRuntime(rt);
if (regMemReport) {
regMemReport = false;
NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(CycleCollector));
sCollectorReporter = new CycleCollectorMultiReporter;
NS_RegisterMemoryMultiReporter(sCollectorReporter);
}
}
void
void
nsCycleCollector_forgetJSRuntime()
{
if (sCollector)
sCollector->ForgetJSRuntime();
if (sCollectorReporter) {
NS_UnregisterMemoryMultiReporter(sCollectorReporter);
sCollectorReporter = nullptr;
}
}
nsPurpleBufferEntry*