Alter AllocSet routines so that requests larger than
ALLOC_BIGCHUNK_LIMIT are always allocated as separate malloc() blocks, and are free()d immediately upon pfree(). Also, if such a chunk is enlarged with repalloc(), translate the operation into a realloc() so as to minimize memory usage. Of course, these large chunks still get freed automatically if the alloc set is reset. I have set ALLOC_BIGCHUNK_LIMIT at 64K for now, but perhaps another size would be better?
This commit is contained in:
parent
1b81fd7793
commit
2b67dc5387
@ -7,18 +7,26 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/utils/mmgr/aset.c,v 1.20 1999/07/17 20:18:13 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/utils/mmgr/aset.c,v 1.21 1999/08/24 20:11:17 tgl Exp $
|
||||
*
|
||||
* NOTE:
|
||||
* This is a new (Feb. 05, 1999) implementation of the allocation set
|
||||
* routines. AllocSet...() does not use OrderedSet...() any more.
|
||||
* Instead it manages allocations in a block pool by itself, combining
|
||||
* many small allocations in a few bigger blocks. AllocSetFree() does
|
||||
* never free() memory really. It just add's the free'd area to some
|
||||
* many small allocations in a few bigger blocks. AllocSetFree() normally
|
||||
* doesn't free() memory really. It just add's the free'd area to some
|
||||
* list for later reuse by AllocSetAlloc(). All memory blocks are free()'d
|
||||
* at once on AllocSetReset(), which happens when the memory context gets
|
||||
* destroyed.
|
||||
* Jan Wieck
|
||||
*
|
||||
* Performance improvement from Tom Lane, 8/99: for extremely large request
|
||||
* sizes, we do want to be able to give the memory back to free() as soon
|
||||
* as it is pfree()'d. Otherwise we risk tying up a lot of memory in
|
||||
* freelist entries that might never be usable. This is specially needed
|
||||
* when the caller is repeatedly repalloc()'ing a block bigger and bigger;
|
||||
* the previous instances of the block were guaranteed to be wasted until
|
||||
* AllocSetReset() under the old way.
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
@ -34,7 +42,9 @@
|
||||
/*--------------------
|
||||
* Chunk freelist k holds chunks of size 1 << (k + ALLOC_MINBITS),
|
||||
* for k = 0 .. ALLOCSET_NUM_FREELISTS-2.
|
||||
* The last freelist holds all larger chunks.
|
||||
* The last freelist holds all larger free chunks. Those chunks come in
|
||||
* varying sizes depending on the request size, whereas smaller chunks are
|
||||
* coerced to powers of 2 to improve their "recyclability".
|
||||
*
|
||||
* CAUTION: ALLOC_MINBITS must be large enough so that
|
||||
* 1<<ALLOC_MINBITS is at least MAXALIGN,
|
||||
@ -51,16 +61,28 @@
|
||||
* The first block allocated for an allocset has size ALLOC_MIN_BLOCK_SIZE.
|
||||
* Each time we have to allocate another block, we double the block size
|
||||
* (if possible, and without exceeding ALLOC_MAX_BLOCK_SIZE), so as to reduce
|
||||
* the load on "malloc".
|
||||
* the bookkeeping load on malloc().
|
||||
*
|
||||
* Blocks allocated to hold oversize chunks do not follow this rule, however;
|
||||
* they are just however big they need to be.
|
||||
* they are just however big they need to be to hold that single chunk.
|
||||
* AllocSetAlloc has some freedom about whether to consider a chunk larger
|
||||
* than ALLOC_SMALLCHUNK_LIMIT to be "oversize". We require all chunks
|
||||
* >= ALLOC_BIGCHUNK_LIMIT to be allocated as single-chunk blocks; those
|
||||
* chunks are treated specially by AllocSetFree and AllocSetRealloc. For
|
||||
* request sizes between ALLOC_SMALLCHUNK_LIMIT and ALLOC_BIGCHUNK_LIMIT,
|
||||
* AllocSetAlloc has discretion whether to put the request into an existing
|
||||
* block or make a single-chunk block.
|
||||
*
|
||||
* We must have ALLOC_MIN_BLOCK_SIZE > ALLOC_SMALLCHUNK_LIMIT and
|
||||
* ALLOC_BIGCHUNK_LIMIT > ALLOC_SMALLCHUNK_LIMIT.
|
||||
*--------------------
|
||||
*/
|
||||
|
||||
#define ALLOC_MIN_BLOCK_SIZE 8192
|
||||
#define ALLOC_MIN_BLOCK_SIZE (8 * 1024)
|
||||
#define ALLOC_MAX_BLOCK_SIZE (8 * 1024 * 1024)
|
||||
|
||||
#define ALLOC_BIGCHUNK_LIMIT (64 * 1024)
|
||||
/* Chunks >= ALLOC_BIGCHUNK_LIMIT are immediately free()d by pfree() */
|
||||
|
||||
#define ALLOC_BLOCKHDRSZ MAXALIGN(sizeof(AllocBlockData))
|
||||
#define ALLOC_CHUNKHDRSZ MAXALIGN(sizeof(AllocChunkData))
|
||||
@ -104,13 +126,6 @@ AllocSetFreeIndex(Size size)
|
||||
* Public routines
|
||||
*/
|
||||
|
||||
/*
|
||||
* AllocPointerIsValid(pointer)
|
||||
* AllocSetIsValid(set)
|
||||
*
|
||||
* .. are now macros in aset.h -cim 4/27/91
|
||||
*/
|
||||
|
||||
/*
|
||||
* AllocSetInit
|
||||
* Initializes given allocation set.
|
||||
@ -141,7 +156,7 @@ AllocSetInit(AllocSet set, AllocMode mode, Size limit)
|
||||
|
||||
/*
|
||||
* AllocSetReset
|
||||
* Frees memory which is allocated in the given set.
|
||||
* Frees all memory which is allocated in the given set.
|
||||
*
|
||||
* Exceptions:
|
||||
* BadArg if set is invalid.
|
||||
@ -195,7 +210,7 @@ AllocSetAlloc(AllocSet set, Size size)
|
||||
{
|
||||
AllocBlock block;
|
||||
AllocChunk chunk;
|
||||
AllocChunk freeref = NULL;
|
||||
AllocChunk priorfree = NULL;
|
||||
int fidx;
|
||||
Size chunk_size;
|
||||
Size blksize;
|
||||
@ -212,7 +227,7 @@ AllocSetAlloc(AllocSet set, Size size)
|
||||
{
|
||||
if (chunk->size >= size)
|
||||
break;
|
||||
freeref = chunk;
|
||||
priorfree = chunk;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -222,10 +237,10 @@ AllocSetAlloc(AllocSet set, Size size)
|
||||
*/
|
||||
if (chunk != NULL)
|
||||
{
|
||||
if (freeref == NULL)
|
||||
if (priorfree == NULL)
|
||||
set->freelist[fidx] = (AllocChunk) chunk->aset;
|
||||
else
|
||||
freeref->aset = chunk->aset;
|
||||
priorfree->aset = chunk->aset;
|
||||
|
||||
chunk->aset = (void *) set;
|
||||
return AllocChunkGetPointer(chunk);
|
||||
@ -241,22 +256,23 @@ AllocSetAlloc(AllocSet set, Size size)
|
||||
Assert(chunk_size >= size);
|
||||
|
||||
/*
|
||||
* If there is enough room in the active allocation block, always
|
||||
* allocate the chunk there.
|
||||
* If there is enough room in the active allocation block, *and*
|
||||
* the chunk is less than ALLOC_BIGCHUNK_LIMIT, put the chunk
|
||||
* into the active allocation block.
|
||||
*/
|
||||
|
||||
if ((block = set->blocks) != NULL)
|
||||
{
|
||||
Size have_free = block->endptr - block->freeptr;
|
||||
|
||||
if (have_free < (chunk_size + ALLOC_CHUNKHDRSZ))
|
||||
if (have_free < (chunk_size + ALLOC_CHUNKHDRSZ) ||
|
||||
chunk_size >= ALLOC_BIGCHUNK_LIMIT)
|
||||
block = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, if requested size exceeds smallchunk limit, allocate an
|
||||
* entire separate block for this allocation
|
||||
*
|
||||
* entire separate block for this allocation. In particular, we will
|
||||
* always take this path if the requested size exceeds bigchunk limit.
|
||||
*/
|
||||
if (block == NULL && size > ALLOC_SMALLCHUNK_LIMIT)
|
||||
{
|
||||
@ -290,7 +306,7 @@ AllocSetAlloc(AllocSet set, Size size)
|
||||
}
|
||||
|
||||
/*
|
||||
* Time to create a new regular block?
|
||||
* Time to create a new regular (multi-chunk) block?
|
||||
*/
|
||||
if (block == NULL)
|
||||
{
|
||||
@ -364,7 +380,6 @@ AllocSetAlloc(AllocSet set, Size size)
|
||||
void
|
||||
AllocSetFree(AllocSet set, AllocPointer pointer)
|
||||
{
|
||||
int fidx;
|
||||
AllocChunk chunk;
|
||||
|
||||
/* AssertArg(AllocSetIsValid(set)); */
|
||||
@ -372,10 +387,42 @@ AllocSetFree(AllocSet set, AllocPointer pointer)
|
||||
AssertArg(AllocSetContains(set, pointer));
|
||||
|
||||
chunk = AllocPointerGetChunk(pointer);
|
||||
fidx = AllocSetFreeIndex(chunk->size);
|
||||
|
||||
chunk->aset = (void *) set->freelist[fidx];
|
||||
set->freelist[fidx] = chunk;
|
||||
if (chunk->size >= ALLOC_BIGCHUNK_LIMIT)
|
||||
{
|
||||
/* Big chunks are certain to have been allocated as single-chunk
|
||||
* blocks. Find the containing block and return it to malloc().
|
||||
*/
|
||||
AllocBlock block = set->blocks;
|
||||
AllocBlock prevblock = NULL;
|
||||
|
||||
while (block != NULL)
|
||||
{
|
||||
if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ))
|
||||
break;
|
||||
prevblock = block;
|
||||
block = block->next;
|
||||
}
|
||||
if (block == NULL)
|
||||
elog(ERROR, "AllocSetFree: cannot find block containing chunk");
|
||||
/* let's just make sure chunk is the only one in the block */
|
||||
Assert(block->freeptr == ((char *) block) +
|
||||
(chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ));
|
||||
/* OK, remove block from aset's list and free it */
|
||||
if (prevblock == NULL)
|
||||
set->blocks = block->next;
|
||||
else
|
||||
prevblock->next = block->next;
|
||||
free(block);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Normal case, put the chunk into appropriate freelist */
|
||||
int fidx = AllocSetFreeIndex(chunk->size);
|
||||
|
||||
chunk->aset = (void *) set->freelist[fidx];
|
||||
set->freelist[fidx] = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -393,7 +440,6 @@ AllocSetFree(AllocSet set, AllocPointer pointer)
|
||||
AllocPointer
|
||||
AllocSetRealloc(AllocSet set, AllocPointer pointer, Size size)
|
||||
{
|
||||
AllocPointer newPointer;
|
||||
Size oldsize;
|
||||
|
||||
/* AssertArg(AllocSetIsValid(set)); */
|
||||
@ -402,23 +448,70 @@ AllocSetRealloc(AllocSet set, AllocPointer pointer, Size size)
|
||||
|
||||
/*
|
||||
* Chunk sizes are aligned to power of 2 on AllocSetAlloc(). Maybe the
|
||||
* allocated area already is >= the new size.
|
||||
*
|
||||
* allocated area already is >= the new size. (In particular, we
|
||||
* always fall out here if the requested size is a decrease.)
|
||||
*/
|
||||
oldsize = AllocPointerGetSize(pointer);
|
||||
if (oldsize >= size)
|
||||
return pointer;
|
||||
|
||||
/* allocate new pointer */
|
||||
newPointer = AllocSetAlloc(set, size);
|
||||
if (oldsize >= ALLOC_BIGCHUNK_LIMIT)
|
||||
{
|
||||
/*
|
||||
* If the chunk is already >= bigchunk limit, then it must have been
|
||||
* allocated as a single-chunk block. Find the containing block and
|
||||
* use realloc() to make it bigger with minimum space wastage.
|
||||
*/
|
||||
AllocChunk chunk = AllocPointerGetChunk(pointer);
|
||||
AllocBlock block = set->blocks;
|
||||
AllocBlock prevblock = NULL;
|
||||
Size blksize;
|
||||
|
||||
/* fill new memory */
|
||||
memmove(newPointer, pointer, (oldsize < size) ? oldsize : size);
|
||||
while (block != NULL)
|
||||
{
|
||||
if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ))
|
||||
break;
|
||||
prevblock = block;
|
||||
block = block->next;
|
||||
}
|
||||
if (block == NULL)
|
||||
elog(ERROR, "AllocSetRealloc: cannot find block containing chunk");
|
||||
/* let's just make sure chunk is the only one in the block */
|
||||
Assert(block->freeptr == ((char *) block) +
|
||||
(chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ));
|
||||
|
||||
/* free old pointer */
|
||||
AllocSetFree(set, pointer);
|
||||
/* Do the realloc */
|
||||
size = MAXALIGN(size);
|
||||
blksize = size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
|
||||
block = (AllocBlock) realloc(block, blksize);
|
||||
if (block == NULL)
|
||||
elog(FATAL, "Memory exhausted in AllocSetReAlloc()");
|
||||
block->freeptr = block->endptr = ((char *) block) + blksize;
|
||||
|
||||
return newPointer;
|
||||
/* Update pointers since block has likely been moved */
|
||||
chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ);
|
||||
if (prevblock == NULL)
|
||||
set->blocks = block;
|
||||
else
|
||||
prevblock->next = block;
|
||||
chunk->size = size;
|
||||
return AllocChunkGetPointer(chunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Normal small-chunk case: just do it by brute force. */
|
||||
|
||||
/* allocate new chunk */
|
||||
AllocPointer newPointer = AllocSetAlloc(set, size);
|
||||
|
||||
/* transfer existing data (certain to fit) */
|
||||
memcpy(newPointer, pointer, oldsize);
|
||||
|
||||
/* free old chunk */
|
||||
AllocSetFree(set, pointer);
|
||||
|
||||
return newPointer;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -15,7 +15,7 @@
|
||||
*
|
||||
* Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: memutils.h,v 1.30 1999/07/15 15:21:41 momjian Exp $
|
||||
* $Id: memutils.h,v 1.31 1999/08/24 20:11:19 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* some of the information in this file will be moved to
|
||||
@ -101,6 +101,12 @@ extern void OrderedElemPushInto(OrderedElem elem, OrderedSet Set);
|
||||
* reallocated. In addition, an allocation set may be reset which
|
||||
* will cause all memory allocated within it to be freed.
|
||||
*
|
||||
* XXX The following material about allocation modes is all OUT OF DATE.
|
||||
* aset.c currently implements only one allocation strategy,
|
||||
* DynamicAllocMode, and that's the only one anyone ever requests anyway.
|
||||
* If we ever did have more strategies, the new ones might or might
|
||||
* not look like what is described here...
|
||||
*
|
||||
* Allocations may occur in four different modes. The mode of
|
||||
* allocation does not affect the behavior of allocations except in
|
||||
* terms of performance. The allocation mode is set at the time of
|
||||
@ -146,7 +152,7 @@ typedef Pointer AllocPointer;
|
||||
* Mode of allocation for an allocation set.
|
||||
*
|
||||
* Note:
|
||||
* See above for a description of the various nodes.
|
||||
* See above for a description of the various modes.
|
||||
*/
|
||||
typedef enum AllocMode
|
||||
{
|
||||
@ -158,23 +164,42 @@ typedef enum AllocMode
|
||||
|
||||
#define DefaultAllocMode DynamicAllocMode
|
||||
|
||||
typedef struct AllocSetData *AllocSet;
|
||||
typedef struct AllocBlockData *AllocBlock;
|
||||
typedef struct AllocChunkData *AllocChunk;
|
||||
|
||||
/*
|
||||
* AllocSet
|
||||
* Allocation set.
|
||||
*/
|
||||
typedef struct AllocSetData
|
||||
{
|
||||
AllocBlock blocks; /* head of list of blocks in this set */
|
||||
#define ALLOCSET_NUM_FREELISTS 8
|
||||
AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* free chunk lists */
|
||||
/* Note: this will change in the future to support other modes */
|
||||
} AllocSetData;
|
||||
|
||||
/*
|
||||
* AllocBlock
|
||||
* Small pieces of memory are taken from bigger blocks of
|
||||
* memory with a size aligned to a power of two. These
|
||||
* pieces are not free's separately, instead they are reused
|
||||
* for the next allocation of a fitting size.
|
||||
* An AllocBlock is the unit of memory that is obtained by aset.c
|
||||
* from malloc(). It contains one or more AllocChunks, which are
|
||||
* the units requested by palloc() and freed by pfree(). AllocChunks
|
||||
* cannot be returned to malloc() individually, instead they are put
|
||||
* on freelists by pfree() and re-used by the next palloc() that has
|
||||
* a matching request size.
|
||||
*
|
||||
* AllocBlockData is the header data for a block --- the usable space
|
||||
* within the block begins at the next alignment boundary.
|
||||
*/
|
||||
typedef struct AllocBlockData
|
||||
{
|
||||
struct AllocSetData *aset;
|
||||
struct AllocBlockData *next;
|
||||
char *freeptr;
|
||||
char *endptr;
|
||||
AllocSet aset; /* aset that owns this block */
|
||||
AllocBlock next; /* next block in aset's blocks list */
|
||||
char *freeptr; /* start of free space in this block */
|
||||
char *endptr; /* end of space in this block */
|
||||
} AllocBlockData;
|
||||
|
||||
typedef AllocBlockData *AllocBlock;
|
||||
|
||||
/*
|
||||
* AllocChunk
|
||||
* The prefix of each piece of memory in an AllocBlock
|
||||
@ -183,26 +208,10 @@ typedef struct AllocChunkData
|
||||
{
|
||||
/* aset is the owning aset if allocated, or the freelist link if free */
|
||||
void *aset;
|
||||
/* size is always the chunk size */
|
||||
/* size is always the size of the usable space in the chunk */
|
||||
Size size;
|
||||
} AllocChunkData;
|
||||
|
||||
typedef AllocChunkData *AllocChunk;
|
||||
|
||||
/*
|
||||
* AllocSet
|
||||
* Allocation set.
|
||||
*/
|
||||
typedef struct AllocSetData
|
||||
{
|
||||
struct AllocBlockData *blocks;
|
||||
#define ALLOCSET_NUM_FREELISTS 8
|
||||
struct AllocChunkData *freelist[ALLOCSET_NUM_FREELISTS];
|
||||
/* Note: this will change in the future to support other modes */
|
||||
} AllocSetData;
|
||||
|
||||
typedef AllocSetData *AllocSet;
|
||||
|
||||
/*
|
||||
* AllocPointerIsValid
|
||||
* True iff pointer is valid allocation pointer.
|
||||
|
Loading…
x
Reference in New Issue
Block a user