diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 28f4417525d7b74b1504bdc9d93c1a33f6ef1359..7c7ba2828d94946549f49ab13cc37a7867bd063f 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_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)); + + /* 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 7a3d12f60528f9952682f380e57a7bd1d8356169..cac5facc313da49c564207bf22be67211c75e611 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.