зеркало из https://github.com/mozilla/pjs.git
bug 146532 Improving recycling allocator
- Uses freelist - 1 int overhead to store size - nsIMemory wrapping r=dougt, sr=darin
This commit is contained in:
Родитель
754b8ba100
Коммит
583c374bfa
|
@ -105,6 +105,7 @@
|
|||
#ifdef GC_LEAK_DETECTOR
|
||||
#include "nsLeakDetector.h"
|
||||
#endif
|
||||
#include "nsRecyclingAllocator.h"
|
||||
|
||||
// Registry Factory creation function defined in nsRegistry.cpp
|
||||
// We hook into this function locally to create and register the registry
|
||||
|
@ -283,7 +284,9 @@ static const nsModuleComponentInfo components[] = {
|
|||
|
||||
COMPONENT(FASTLOADSERVICE, nsFastLoadService::Create),
|
||||
COMPONENT(VARIANT, nsVariantConstructor),
|
||||
COMPONENT(INTERFACEINFOMANAGER_SERVICE, nsXPTIInterfaceInfoManagerGetSingleton)
|
||||
COMPONENT(INTERFACEINFOMANAGER_SERVICE, nsXPTIInterfaceInfoManagerGetSingleton),
|
||||
|
||||
COMPONENT(RECYCLINGALLOCATOR, nsRecyclingAllocatorImpl::nsRecyclingAllocatorImplConstructor),
|
||||
};
|
||||
|
||||
#undef COMPONENT
|
||||
|
|
|
@ -7,6 +7,7 @@ nsIObserverService.idl
|
|||
nsIPersistentProperties2.idl
|
||||
nsIProperties.idl
|
||||
nsIPropertyBag.idl
|
||||
nsIRecyclingAllocator.idl
|
||||
nsISerializable.idl
|
||||
nsISimpleEnumerator.idl
|
||||
nsISupportsArray.idl
|
||||
|
|
|
@ -116,6 +116,7 @@ XPIDLSRCS = \
|
|||
nsIPersistentProperties2.idl \
|
||||
nsIProperties.idl \
|
||||
nsIPropertyBag.idl \
|
||||
nsIRecyclingAllocator.idl \
|
||||
nsIVariant.idl \
|
||||
nsISerializable.idl \
|
||||
nsISupportsArray.idl \
|
||||
|
|
|
@ -75,6 +75,7 @@ XPIDLSRCS = \
|
|||
.\nsITimelineService.idl \
|
||||
.\nsIProperties.idl \
|
||||
.\nsIPropertyBag.idl \
|
||||
.\nsIRecyclingAllocator.idl \
|
||||
.\nsISerializable.idl \
|
||||
.\nsISimpleEnumerator.idl \
|
||||
.\nsISupportsArray.idl \
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Netscape Public License
|
||||
* Version 1.1 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/NPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Netscape Communications Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2002
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s): Suresh Duddi <dp@netscape.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the NPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the NPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsIMemory.idl"
|
||||
|
||||
/**
|
||||
*
|
||||
* nsIRecyclingAllocator: A wrapper for the nsRecyclingAllocator
|
||||
*
|
||||
* Holds allocations and reuses them for subsequent allocs.
|
||||
* Thread safe and uses a timer to release freelist.
|
||||
*
|
||||
* @status UNDER-DEVELOPMENT
|
||||
*/
|
||||
|
||||
[scriptable, uuid(d064a04c-9cee-4319-be31-64d565bccba9)]
|
||||
interface nsIRecyclingAllocator : nsIMemory
|
||||
{
|
||||
void init(in size_t nblocks, in size_t recycleAfter, in string id);
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_RECYCLINGALLOCATOR_CID \
|
||||
{ /* ac07ed4c-bf17-4976-a15c-d2194db3b1bf */ \
|
||||
0xac07ed4c, \
|
||||
0xbf17, \
|
||||
0x4976, \
|
||||
{0xa1, 0x5c, 0xd2, 0x19, 0x4d, 0xb3, 0xb1, 0xbf} }
|
||||
|
||||
#define NS_RECYCLINGALLOCATOR_CLASSNAME "Recycling Allocator"
|
||||
|
||||
#define NS_RECYCLINGALLOCATOR_CONTRACTID "@mozilla.org/recycling-allocator;1"
|
||||
%}
|
|
@ -45,6 +45,9 @@
|
|||
#include <stdio.h>
|
||||
#include "nsRecyclingAllocator.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsIMemory.h"
|
||||
#include "nsAutoLock.h"
|
||||
#include "prprf.h"
|
||||
|
||||
#define NS_SEC_TO_MS(s) ((s) * 1000)
|
||||
|
||||
|
@ -54,17 +57,14 @@ nsRecyclingAllocator::nsRecycleTimerCallback(nsITimer *aTimer, void *aClosure)
|
|||
nsRecyclingAllocator *obj = (nsRecyclingAllocator *) aClosure;
|
||||
if (!obj->mTouched)
|
||||
{
|
||||
#ifdef DEBUG_dp
|
||||
printf("DEBUG: nsRecyclingAllocator not touched: %d allocations avaliable\n", obj->mNAllocations);
|
||||
#endif
|
||||
if (obj->mNAllocations)
|
||||
if (obj->mFreeList)
|
||||
obj->FreeUnusedBuckets();
|
||||
|
||||
// If we are holding no more memory, there is not need for the timer.
|
||||
// If we are holding no more memory, there is no need for the timer.
|
||||
// We will revive the timer on the next allocation.
|
||||
// XXX Unfortunately there is no way to Cancel and restart the same timer.
|
||||
// XXX So we pretty much kill it and create a new one later.
|
||||
if (!obj->mNAllocations && obj->mRecycleTimer)
|
||||
if (!obj->mFreeList && obj->mRecycleTimer)
|
||||
{
|
||||
obj->mRecycleTimer->Cancel();
|
||||
NS_RELEASE(obj->mRecycleTimer);
|
||||
|
@ -79,186 +79,129 @@ nsRecyclingAllocator::nsRecycleTimerCallback(nsITimer *aTimer, void *aClosure)
|
|||
}
|
||||
|
||||
|
||||
nsRecyclingAllocator::nsRecyclingAllocator(PRUint32 nbucket, PRUint32 recycleAfter, const char *id)
|
||||
: mNBucket(nbucket), mRecycleTimer(nsnull), mRecycleAfter(recycleAfter), mTouched(0),
|
||||
mNAllocations(0), mId(id)
|
||||
nsRecyclingAllocator::nsRecyclingAllocator(PRUint32 nbucket, PRUint32 recycleAfter, const char *id) :
|
||||
mMaxBlocks(nbucket), mBlocks(nsnull), mFreeList(nsnull), mNotUsedList(nsnull),
|
||||
mRecycleTimer(nsnull), mRecycleAfter(recycleAfter), mTouched(0), mId(id)
|
||||
#ifdef DEBUG
|
||||
,mNAllocated(0)
|
||||
#endif
|
||||
{
|
||||
NS_ASSERTION(mNBucket <= NS_MAX_BUCKETS, "Too many buckets. This will affect the allocator's performance.");
|
||||
if (mNBucket > NS_MAX_BUCKETS)
|
||||
{
|
||||
mNBucket = NS_MAX_BUCKETS;
|
||||
}
|
||||
mMemBucket = (nsMemBucket *) calloc(mNBucket, sizeof(nsMemBucket));
|
||||
if (!mMemBucket)
|
||||
mNBucket = 0;
|
||||
else
|
||||
{
|
||||
for (PRUint32 i = 0; i < mNBucket; i++)
|
||||
mMemBucket[i].available = 1;
|
||||
NS_ASSERTION(mMaxBlocks <= NS_MAX_BLOCKS, "Too many blocks. This will affect the allocator's performance.");
|
||||
|
||||
mLock = PR_NewLock();
|
||||
NS_ASSERTION(mLock, "Recycling allocator cannot get lock");
|
||||
|
||||
Init(nbucket, recycleAfter, id);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsRecyclingAllocator::Init(PRUint32 nbucket, PRUint32 recycleAfter, const char *id)
|
||||
{
|
||||
nsAutoLock lock(mLock);
|
||||
|
||||
// Free all memory held, if any
|
||||
while(mFreeList)
|
||||
{
|
||||
free(mFreeList->block);
|
||||
mFreeList = mFreeList->next;
|
||||
}
|
||||
mFreeList = nsnull;
|
||||
|
||||
if (mBlocks)
|
||||
delete [] mBlocks;
|
||||
|
||||
// Reinitialize everything
|
||||
mMaxBlocks = nbucket;
|
||||
if (nbucket)
|
||||
{
|
||||
// Create memory for our bookkeeping
|
||||
mBlocks = new BlockStoreNode[mMaxBlocks];
|
||||
if (!mBlocks)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
// Link them together
|
||||
mNotUsedList = mBlocks;
|
||||
for (PRUint32 i=0; i < mMaxBlocks-1; i++)
|
||||
mBlocks[i].next = &(mBlocks[i+1]);
|
||||
}
|
||||
|
||||
mRecycleAfter = recycleAfter;
|
||||
mId = id;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRecyclingAllocator::~nsRecyclingAllocator()
|
||||
{
|
||||
// Cancel and destroy recycle timer
|
||||
if (mRecycleTimer) {
|
||||
if (mRecycleTimer)
|
||||
{
|
||||
mRecycleTimer->Cancel();
|
||||
NS_RELEASE(mRecycleTimer);
|
||||
}
|
||||
|
||||
// Free all memory held, if any
|
||||
if (mNAllocations)
|
||||
for (PRUint32 i = 0; i < mNBucket; i++)
|
||||
{
|
||||
PRBool claimed = Claim(i);
|
||||
|
||||
// ASSERT that we claimed the bucked. If we cannot, then the bucket is in use.
|
||||
// We dont attempt to free this ptr.
|
||||
// This will most likely cause a leak of this memory.
|
||||
NS_ASSERTION(claimed, "~nsRecycledAllocator: Bucket memory still in use.");
|
||||
|
||||
// Free bucket memory if not in use
|
||||
if (claimed && mMemBucket[i].ptr)
|
||||
free(mMemBucket[i].ptr);
|
||||
}
|
||||
while(mFreeList)
|
||||
{
|
||||
free(mFreeList->block);
|
||||
mFreeList = mFreeList->next;
|
||||
}
|
||||
mFreeList = nsnull;
|
||||
|
||||
if (mBlocks)
|
||||
delete [] mBlocks;
|
||||
|
||||
// Free memory for buckets
|
||||
if (mMemBucket)
|
||||
free(mMemBucket);
|
||||
if (mLock)
|
||||
{
|
||||
PR_DestroyLock(mLock);
|
||||
mLock = nsnull;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocation and free routines
|
||||
|
||||
void*
|
||||
nsRecyclingAllocator::Malloc(PRUint32 bytes, PRBool zeroit)
|
||||
{
|
||||
PRInt32 availableBucketIndex = -1;
|
||||
PRUint32 i;
|
||||
PRUint32 size;
|
||||
void *ptr;
|
||||
|
||||
// Mark that we are using. This will prevent any
|
||||
// timer based release of unused memory.
|
||||
Touch();
|
||||
|
||||
for (i = 0; i < mNBucket; i++)
|
||||
{
|
||||
size = mMemBucket[i].size;
|
||||
ptr = mMemBucket[i].ptr;
|
||||
|
||||
// Dont look at buckets with no memory allocated or less memory than
|
||||
// what we need.
|
||||
// Can we do this check without claiming the bucket? I think we can
|
||||
// because the next thing we do is try claim this bucket.
|
||||
if (!ptr || size < bytes)
|
||||
continue;
|
||||
|
||||
// Try Claim a bucket. If we cant, skip it.
|
||||
if (!Claim(i))
|
||||
continue;
|
||||
|
||||
if (size == bytes)
|
||||
{
|
||||
// Let go of any freeAllocatedBucket that we claimed
|
||||
if (availableBucketIndex >= 0)
|
||||
Unclaim(availableBucketIndex);
|
||||
if (zeroit)
|
||||
memset(ptr, bytes, 0);
|
||||
return ptr;
|
||||
}
|
||||
// Meanwhile, remember a free allocated bucket.
|
||||
// At this point we know the bucket is not in use and it has more
|
||||
// than what we need
|
||||
if (availableBucketIndex < 0)
|
||||
availableBucketIndex = i;
|
||||
else
|
||||
{
|
||||
// See if this bucket is closer to what we need
|
||||
if (size < mMemBucket[availableBucketIndex].size)
|
||||
{
|
||||
Unclaim(availableBucketIndex);
|
||||
availableBucketIndex = i;
|
||||
}
|
||||
else
|
||||
// Undo our claim as availableBucketIndex does better than this one
|
||||
Unclaim(i);
|
||||
}
|
||||
}
|
||||
|
||||
// See if we have an allocated bucket
|
||||
if (availableBucketIndex >= 0) {
|
||||
ptr = mMemBucket[availableBucketIndex].ptr;
|
||||
memset(ptr, bytes, 0);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// We dont have that memory already
|
||||
// Allocate.
|
||||
ptr = zeroit ? calloc(1, bytes) : malloc(bytes);
|
||||
|
||||
// Take care of no memory and no free slot situation
|
||||
if (!ptr || mNAllocations == (PRInt32)mNBucket)
|
||||
{
|
||||
#ifdef DEBUG_dp
|
||||
// Warn if we are failing over to malloc and not storing it
|
||||
// This says we have a misdesigned memory pool. The intent was
|
||||
// once the pool was full, we would never fail over to calloc.
|
||||
printf("nsRecyclingAllocator(%s) malloc %d - FAILOVER 0x%p Memory pool has sizes: ",
|
||||
mId, bytes, ptr);
|
||||
for (i = 0; i < mNBucket; i++)
|
||||
{
|
||||
printf("%d ", mMemBucket[i].size);
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Find a free unallocated bucket and store allocation
|
||||
for (i = 0; i < mNBucket; i++)
|
||||
Block* freeBlock = FindFreeBlock(bytes);
|
||||
if (freeBlock)
|
||||
{
|
||||
// If bucket cannot be claimed, continue search.
|
||||
if (!Claim(i))
|
||||
continue;
|
||||
|
||||
if (!mMemBucket[i].ptr)
|
||||
{
|
||||
// Found free slot. Store it
|
||||
mMemBucket[i].ptr = ptr;
|
||||
mMemBucket[i].size = bytes;
|
||||
PR_AtomicIncrement(&mNAllocations);
|
||||
// This is the first allocation we are holding.
|
||||
// Setup timer for releasing memory
|
||||
// If this fails, then we wont have a timer to release unused
|
||||
// memory. We can live with that. Also, the next allocation
|
||||
// will try again to set the timer.
|
||||
if (mNAllocations && !mRecycleTimer)
|
||||
{
|
||||
(void) NS_NewTimer(&mRecycleTimer, nsRecycleTimerCallback, this,
|
||||
NS_SEC_TO_MS(mRecycleAfter), PR_TRUE,
|
||||
NS_TYPE_REPEATING_SLACK);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// This is an already allocated bucket. Cant use this one.
|
||||
Unclaim(i);
|
||||
return DATA(freeBlock);
|
||||
}
|
||||
#ifdef DEBUG_dp
|
||||
// Warn if we are failing over to malloc and not storing it
|
||||
// This says we have a misdesigned memory pool. The intent was
|
||||
// once the pool was full, we would never fail over to calloc.
|
||||
printf("nsRecyclingAllocator(%s) malloc %d - FAILOVER 0x%p Memory pool has sizes: ",
|
||||
mId, bytes, ptr);
|
||||
for (i = 0; i < mNBucket; i++)
|
||||
|
||||
// We need to do an allocation
|
||||
// Add 4 bytes to what we allocate to hold the bucket index
|
||||
PRUint32 allocBytes = bytes + NS_ALLOCATOR_OVERHEAD_BYTES;
|
||||
|
||||
// We dont have that memory already. Allocate.
|
||||
Block *ptr = (Block *) (zeroit ? calloc(1, allocBytes) : malloc(allocBytes));
|
||||
|
||||
// Deal with no memory situation
|
||||
if (!ptr)
|
||||
return ptr;
|
||||
|
||||
// This is the first allocation we are holding.
|
||||
// Setup timer for releasing memory
|
||||
// If this fails, then we wont have a timer to release unused
|
||||
// memory. We can live with that. Also, the next allocation
|
||||
// will try again to set the timer.
|
||||
if (mRecycleAfter && !mRecycleTimer)
|
||||
{
|
||||
printf("%d ", mMemBucket[i].size);
|
||||
(void) NS_NewTimer(&mRecycleTimer, nsRecycleTimerCallback, this,
|
||||
NS_SEC_TO_MS(mRecycleAfter), PR_TRUE,
|
||||
NS_TYPE_REPEATING_SLACK);
|
||||
NS_ASSERTION(mRecycleTimer, "nsRecyclingAllocator: Creating timer failed.\n");
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
#ifdef DEBUG
|
||||
mNAllocated++;
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
|
||||
// Store size and return data portion
|
||||
ptr->bytes = bytes;
|
||||
return DATA(ptr);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -268,28 +211,23 @@ nsRecyclingAllocator::Free(void *ptr)
|
|||
// timer based release of unused memory.
|
||||
Touch();
|
||||
|
||||
for (PRUint32 i = 0; i < mNBucket; i++)
|
||||
{
|
||||
if (mMemBucket[i].ptr == ptr)
|
||||
{
|
||||
// Ah ha. One of the slots we allocated.
|
||||
// Nothing to do. Mark it unused.
|
||||
Unclaim(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Block* block = DATA_TO_BLOCK(ptr);
|
||||
|
||||
#ifdef DEBUG_dp
|
||||
// Warn if we are failing over to free
|
||||
printf("nsRecyclingAllocator(%s) free - FAILOVER 0x%p Memory pool has sizes: ", mId, ptr);
|
||||
for (i = 0; i < mNBucket; i++)
|
||||
if (!AddToFreeList(block))
|
||||
{
|
||||
printf("%d ", mMemBucket[i].size);
|
||||
}
|
||||
printf("\n");
|
||||
// We are holding more than max. Failover to free
|
||||
#ifdef DEBUG_dp
|
||||
char buf[1024];
|
||||
// Warn if we are failing over to malloc/free and not storing it
|
||||
// This says we have a misdesigned memory pool. The intent was
|
||||
// once the pool was full, we would never fail over to calloc.
|
||||
PR_snprintf(buf, sizeof(buf), "nsRecyclingAllocator(%s) FAILOVER 0x%p (%d) - %d allocations, %d max\n",
|
||||
mId, (char *)ptr, block->bytes, mNAllocated, mMaxBlocks);
|
||||
NS_WARNING(buf);
|
||||
mNAllocated--;
|
||||
#endif
|
||||
// Failover to free
|
||||
free(ptr);
|
||||
free(block);
|
||||
}
|
||||
}
|
||||
|
||||
/* FreeUnusedBuckets
|
||||
|
@ -303,28 +241,182 @@ nsRecyclingAllocator::FreeUnusedBuckets()
|
|||
#ifdef DEBUG_dp
|
||||
printf("DEBUG: nsRecyclingAllocator(%s) FreeUnusedBuckets: ", mId);
|
||||
#endif
|
||||
if (!mNAllocations)
|
||||
return;
|
||||
nsAutoLock lock(mLock);
|
||||
|
||||
for (PRUint32 i = 0; i < mNBucket; i++)
|
||||
// We will run through the freelist and free all blocks
|
||||
BlockStoreNode* node = mFreeList;
|
||||
while (node)
|
||||
{
|
||||
if (Claim(i))
|
||||
{
|
||||
if (mMemBucket[i].ptr)
|
||||
{
|
||||
// Free the allocated block
|
||||
free(node->block);
|
||||
|
||||
#ifdef DEBUG_dp
|
||||
printf("%d ", mMemBucket[i].size);
|
||||
printf("%d ", node->bytes);
|
||||
#endif
|
||||
free(mMemBucket[i].ptr);
|
||||
mMemBucket[i].ptr = nsnull;
|
||||
mMemBucket[i].size = 0;
|
||||
PR_AtomicDecrement(&mNAllocations);
|
||||
}
|
||||
Unclaim(i);
|
||||
}
|
||||
// Clear Node
|
||||
node->block = nsnull;
|
||||
node->bytes = 0;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
// remake the lists
|
||||
mNotUsedList = mBlocks;
|
||||
for (PRUint32 i=0; i < mMaxBlocks-1; i++)
|
||||
mBlocks[i].next = &(mBlocks[i+1]);
|
||||
mBlocks[mMaxBlocks-1].next = nsnull;
|
||||
mFreeList = nsnull;
|
||||
|
||||
#ifdef DEBUG
|
||||
mNAllocated = 0;
|
||||
#endif
|
||||
#ifdef DEBUG_dp
|
||||
printf("\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
inline nsRecyclingAllocator::Block*
|
||||
nsRecyclingAllocator::FindFreeBlock(PRUint32 bytes)
|
||||
{
|
||||
// We dont enter lock for this check. This is intentional.
|
||||
// Here is my logic: we are checking if (!mFreeList). Doing this check
|
||||
// without locking can lead to unpredictable results. YES. But the effect
|
||||
// of the unpredictedness are ok. here is why:
|
||||
//
|
||||
// a) if the check returned NULL when there is stuff in freelist
|
||||
// We would just end up reallocating.
|
||||
//
|
||||
// b) if the check returned nonNULL when our freelist is empty
|
||||
// This is the more likely and dangerous case. The code for
|
||||
// FindFreeBlock() will enter lock, while (null) and return null.
|
||||
//
|
||||
// The reason why I chose to not enter lock for this check was that when
|
||||
// the allocator is full, we dont want to impose any more overhead than
|
||||
// we already are for failing over to malloc/free.
|
||||
|
||||
if (!mFreeList)
|
||||
return NULL;
|
||||
|
||||
Block *block = nsnull;
|
||||
|
||||
nsAutoLock lock(mLock);
|
||||
BlockStoreNode* freeNode = mFreeList;
|
||||
BlockStoreNode** prevp = &mFreeList;
|
||||
|
||||
while (freeNode)
|
||||
{
|
||||
if (freeNode->bytes >= bytes)
|
||||
{
|
||||
// Found the best fit free block
|
||||
block = freeNode->block;
|
||||
|
||||
// Clear the free node
|
||||
freeNode->block = nsnull;
|
||||
freeNode->bytes = 0;
|
||||
|
||||
// Remove free node from free list
|
||||
*prevp = freeNode->next;
|
||||
|
||||
// Add removed BlockStoreNode to not used list
|
||||
freeNode->next = mNotUsedList;
|
||||
mNotUsedList = freeNode;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
prevp = &(freeNode->next);
|
||||
freeNode = freeNode->next;
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
inline PRInt32
|
||||
nsRecyclingAllocator::AddToFreeList(Block* block)
|
||||
{
|
||||
// Make sure we arent keeping more than mMaxBlocks
|
||||
if (!mNotUsedList)
|
||||
return PR_FALSE;
|
||||
|
||||
nsAutoLock lock(mLock);
|
||||
|
||||
// Pick a node from the not used list
|
||||
BlockStoreNode *node = mNotUsedList;
|
||||
mNotUsedList = mNotUsedList->next;
|
||||
|
||||
// Initialize the node
|
||||
node->bytes = block->bytes;
|
||||
node->block = block;
|
||||
|
||||
// Find the right spot in the sorted list.
|
||||
BlockStoreNode* freeNode = mFreeList;
|
||||
BlockStoreNode** prevp = &mFreeList;
|
||||
while (freeNode)
|
||||
{
|
||||
if (freeNode->bytes >= block->bytes)
|
||||
break;
|
||||
prevp = &(freeNode->next);
|
||||
freeNode = freeNode->next;
|
||||
}
|
||||
|
||||
// Needs to be inserted between *prevp and freeNode
|
||||
*prevp = node;
|
||||
node->next = freeNode;
|
||||
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Wrapping the recyling allocator with nsIMemory
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// nsIMemory methods
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS2(nsRecyclingAllocatorImpl, nsIMemory, nsIRecyclingAllocator);
|
||||
|
||||
NS_IMETHODIMP_(void *)
|
||||
nsRecyclingAllocatorImpl::Alloc(PRSize size)
|
||||
{
|
||||
if (size < 0)
|
||||
return NULL;
|
||||
else
|
||||
return nsRecyclingAllocatorImpl::Malloc(size, PR_FALSE);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(void *)
|
||||
nsRecyclingAllocatorImpl::Realloc(void *ptr, PRSize size)
|
||||
{
|
||||
// XXX Not yet implemented
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(void)
|
||||
nsRecyclingAllocatorImpl::Free(void *ptr)
|
||||
{
|
||||
nsRecyclingAllocator::Free(ptr);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsRecyclingAllocatorImpl::Init(size_t nbuckets, size_t recycleAfter, const char *id)
|
||||
{
|
||||
// normalize input
|
||||
if (nbuckets < 0)
|
||||
nbuckets = 0;
|
||||
if (recycleAfter < 0)
|
||||
recycleAfter = 0;
|
||||
|
||||
return nsRecyclingAllocator::Init((PRUint32) nbuckets, (PRUint32) recycleAfter, id);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsRecyclingAllocatorImpl::HeapMinimize(PRBool immediate)
|
||||
{
|
||||
// XXX Not yet implemented
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsRecyclingAllocatorImpl::IsLowMemory(PRBool *lowmemoryb_ptr)
|
||||
{
|
||||
// XXX Not yet implemented
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,11 +50,13 @@
|
|||
* Uses a timer to release all memory allocated if not used for more than
|
||||
* 10 secs automatically.
|
||||
*
|
||||
* Also there is a 4 byte maintenance overhead on every allocation.
|
||||
*
|
||||
* This allocator is thread safe.
|
||||
*
|
||||
* CAVEATS: As the number of buckets increases, this allocators performance
|
||||
* will drop. As a general guideline, dont use this for more
|
||||
* than NS_MAX_BUCKETS (10)
|
||||
* than NS_MAX_BLOCKS
|
||||
*/
|
||||
|
||||
#ifndef nsRecyclingAllocator_h__
|
||||
|
@ -62,55 +64,87 @@
|
|||
|
||||
#include "nscore.h"
|
||||
#include "pratom.h"
|
||||
#include "prlock.h"
|
||||
#include "nsIRecyclingAllocator.h"
|
||||
#include "nsIGenericFactory.h"
|
||||
|
||||
#define NS_DEFAULT_RECYCLE_TIMEOUT 10 // secs
|
||||
#define NS_MAX_BUCKETS 10
|
||||
#define NS_MAX_BLOCKS 24
|
||||
#define NS_ALLOCATOR_OVERHEAD_BYTES (sizeof(Block)) // bytes
|
||||
|
||||
class nsITimer;
|
||||
class nsIMemory;
|
||||
|
||||
class NS_COM nsRecyclingAllocator {
|
||||
protected:
|
||||
struct nsMemBucket {
|
||||
void *ptr;
|
||||
PRUint32 size;
|
||||
|
||||
// Is this bucket available ?
|
||||
// 1 : free
|
||||
// 0 or negative : in use
|
||||
// This is an int because we use atomic inc/dec to operate on it
|
||||
PRInt32 available;
|
||||
struct Block {
|
||||
PRUint32 bytes;
|
||||
};
|
||||
|
||||
PRUint32 mNBucket;
|
||||
nsMemBucket *mMemBucket;
|
||||
struct BlockStoreNode {
|
||||
BlockStoreNode() : bytes(0), block(nsnull), next(nsnull) {};
|
||||
PRUint32 bytes;
|
||||
Block *block;
|
||||
BlockStoreNode *next;
|
||||
};
|
||||
|
||||
#define DATA(block) ((void *)(((char *)block) + NS_ALLOCATOR_OVERHEAD_BYTES))
|
||||
#define DATA_TO_BLOCK(data) ((Block *)((char *)(data) - NS_ALLOCATOR_OVERHEAD_BYTES))
|
||||
|
||||
// mMaxBlocks: Maximum number of blocks that can be allocated
|
||||
PRUint32 mMaxBlocks;
|
||||
|
||||
// mBlocks:
|
||||
// All blocks used or not.
|
||||
BlockStoreNode *mBlocks;
|
||||
|
||||
// mFreeList
|
||||
// A linked list of free blocks sorted by increasing order of size
|
||||
BlockStoreNode* mFreeList;
|
||||
|
||||
// mNotUsedList
|
||||
// A linked list of BlockStoreNodes that are not used to store
|
||||
// any block information. When we add blocks into mFreeList, we
|
||||
// take BlockStoreNode from here.
|
||||
BlockStoreNode* mNotUsedList;
|
||||
|
||||
// mLock: Thread safety of mFreeList and mNotUsedList
|
||||
PRLock *mLock;
|
||||
|
||||
// Timer for freeing unused memory
|
||||
nsITimer *mRecycleTimer;
|
||||
|
||||
// mRecycleAfter:
|
||||
// Allocator should be untouched for this many seconds for freeing
|
||||
// unused Blocks.
|
||||
PRUint32 mRecycleAfter;
|
||||
|
||||
// mTouched : says if the allocator touched any bucket
|
||||
// If allocator didn't touch any bucket over a time
|
||||
// time interval, timer will call FreeUnusedBuckets()
|
||||
// mTouched:
|
||||
// says if the allocator touched any bucket. If allocator didn't touch
|
||||
// any bucket over a time time interval, timer will call FreeUnusedBuckets()
|
||||
PRInt32 mTouched;
|
||||
|
||||
// mNAllocations: says how many buckets still have memory.
|
||||
// If all buckets were freed the last time
|
||||
// around and allocator didn't touch any bucket, then
|
||||
// there is nothing to be freed.
|
||||
PRInt32 mNAllocations;
|
||||
|
||||
// mId: a string for identifying the user of nsRecyclingAllocator
|
||||
// User mainly for debug prints
|
||||
// mId:
|
||||
// a string for identifying the user of nsRecyclingAllocator
|
||||
// User mainly for debug prints
|
||||
const char *mId;
|
||||
|
||||
#ifdef DEBUG
|
||||
// mNAllocated: Number of blocks allocated
|
||||
PRInt32 mNAllocated;
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// nbucket : number of buckets to hold. Capped at NS_MAX_BUCKET
|
||||
// recycleAfter : Try recycling allocated buckets after this many seconds
|
||||
// id : a string used to identify debug prints. Will not be released.
|
||||
nsRecyclingAllocator(PRUint32 nbucket, PRUint32 recycleAfter = NS_DEFAULT_RECYCLE_TIMEOUT,
|
||||
nsRecyclingAllocator(PRUint32 nbucket = 0, PRUint32 recycleAfter = NS_DEFAULT_RECYCLE_TIMEOUT,
|
||||
const char *id = NULL);
|
||||
~nsRecyclingAllocator();
|
||||
|
||||
NS_IMETHOD Init(PRUint32 nbucket, PRUint32 recycleAfter, const char *id);
|
||||
|
||||
// Allocation and free routines
|
||||
void* Malloc(PRUint32 size, PRBool zeroit = PR_FALSE);
|
||||
void Free(void *ptr);
|
||||
|
@ -120,27 +154,20 @@ class NS_COM nsRecyclingAllocator {
|
|||
return Malloc(items * size, PR_TRUE);
|
||||
}
|
||||
|
||||
|
||||
// FreeUnusedBuckets - Frees any bucket memory that isn't in use
|
||||
void FreeUnusedBuckets();
|
||||
|
||||
protected:
|
||||
// Timer callback to trigger unused memory
|
||||
|
||||
// Timer callback to trigger unused memory
|
||||
static void nsRecycleTimerCallback(nsITimer *aTimer, void *aClosure);
|
||||
|
||||
// Bucket handling
|
||||
PRBool Claim(PRUint32 i)
|
||||
{
|
||||
PRBool claimed = (PR_AtomicDecrement(&mMemBucket[i].available) == 0);
|
||||
// Undo the claim, if claim failed
|
||||
if (!claimed)
|
||||
Unclaim(i);
|
||||
|
||||
return claimed;
|
||||
}
|
||||
|
||||
void Unclaim(PRUint32 i) { PR_AtomicIncrement(&mMemBucket[i].available); }
|
||||
// Freelist management
|
||||
// FindFreeBlock: return a free block that can hold bytes (best fit)
|
||||
Block* FindFreeBlock(PRUint32 bytes);
|
||||
// AddToFreeList: adds block into our freelist for future retrieval.
|
||||
// Returns PR_TRUE is addition was successful. PR_FALSE otherewise.
|
||||
PRBool AddToFreeList(Block* block);
|
||||
|
||||
// Touch will mark that someone used this allocator
|
||||
// Timer based release will free unused memory only if allocator
|
||||
|
@ -156,4 +183,24 @@ class NS_COM nsRecyclingAllocator {
|
|||
friend void nsRecycleTimerCallback(nsITimer *aTimer, void *aClosure);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Wrapping the recyling allocator with nsIMemory
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// Wrapping the nsRecyclingAllocator with nsIMemory
|
||||
class NS_COM nsRecyclingAllocatorImpl : public nsRecyclingAllocator, public nsIRecyclingAllocator {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIMEMORY
|
||||
NS_DECL_NSIRECYCLINGALLOCATOR
|
||||
|
||||
nsRecyclingAllocatorImpl()
|
||||
{
|
||||
NS_INIT_ISUPPORTS();
|
||||
}
|
||||
|
||||
virtual ~nsRecyclingAllocatorImpl() {}
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsRecyclingAllocatorImpl);
|
||||
};
|
||||
#endif // nsRecyclingAllocator_h__
|
||||
|
|
|
@ -1305,6 +1305,13 @@
|
|||
<FILEKIND>Text</FILEKIND>
|
||||
<FILEFLAGS></FILEFLAGS>
|
||||
</FILE>
|
||||
<FILE>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
<PATH>nsIRecyclingAllocator.idl</PATH>
|
||||
<PATHFORMAT>MacOS</PATHFORMAT>
|
||||
<FILEKIND>Text</FILEKIND>
|
||||
<FILEFLAGS></FILEFLAGS>
|
||||
</FILE>
|
||||
<FILE>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
<PATH>nsIStringStream.idl</PATH>
|
||||
|
@ -1703,6 +1710,11 @@
|
|||
<PATH>nsIPropertyBag.idl</PATH>
|
||||
<PATHFORMAT>MacOS</PATHFORMAT>
|
||||
</FILEREF>
|
||||
<FILEREF>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
<PATH>nsIRecyclingAllocator.idl</PATH>
|
||||
<PATHFORMAT>MacOS</PATHFORMAT>
|
||||
</FILEREF>
|
||||
<FILEREF>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
<PATH>nsIStringStream.idl</PATH>
|
||||
|
@ -2972,6 +2984,13 @@
|
|||
<FILEKIND>Text</FILEKIND>
|
||||
<FILEFLAGS></FILEFLAGS>
|
||||
</FILE>
|
||||
<FILE>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
<PATH>nsIRecyclingAllocator.idl</PATH>
|
||||
<PATHFORMAT>MacOS</PATHFORMAT>
|
||||
<FILEKIND>Text</FILEKIND>
|
||||
<FILEFLAGS></FILEFLAGS>
|
||||
</FILE>
|
||||
<FILE>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
<PATH>nsIStringStream.idl</PATH>
|
||||
|
@ -3370,6 +3389,11 @@
|
|||
<PATH>nsIPropertyBag.idl</PATH>
|
||||
<PATHFORMAT>MacOS</PATHFORMAT>
|
||||
</FILEREF>
|
||||
<FILEREF>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
<PATH>nsIRecyclingAllocator.idl</PATH>
|
||||
<PATHFORMAT>MacOS</PATHFORMAT>
|
||||
</FILEREF>
|
||||
<FILEREF>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
<PATH>nsIStringStream.idl</PATH>
|
||||
|
@ -3845,6 +3869,12 @@
|
|||
<PATH>nsIPropertyBag.idl</PATH>
|
||||
<PATHFORMAT>MacOS</PATHFORMAT>
|
||||
</FILEREF>
|
||||
<FILEREF>
|
||||
<TARGETNAME>headers</TARGETNAME>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
<PATH>nsIRecyclingAllocator.idl</PATH>
|
||||
<PATHFORMAT>MacOS</PATHFORMAT>
|
||||
</FILEREF>
|
||||
<FILEREF>
|
||||
<TARGETNAME>headers</TARGETNAME>
|
||||
<PATHTYPE>Name</PATHTYPE>
|
||||
|
|
Загрузка…
Ссылка в новой задаче