aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/utils/mmgr/aset.c173
-rw-r--r--src/include/utils/memutils.h67
2 files changed, 171 insertions, 69 deletions
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 28f4417525d..7c7ba2828d9 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -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))
@@ -105,13 +127,6 @@ AllocSetFreeIndex(Size size)
*/
/*
- * 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));
+
+ /* 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;
- /* free old pointer */
- AllocSetFree(set, pointer);
+ /* 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);
- return newPointer;
+ /* free old chunk */
+ AllocSetFree(set, pointer);
+
+ return newPointer;
+ }
}
/*
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 7a3d12f6052..cac5facc313 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -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.