1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-12 05:01:15 +03:00
Files
postgres/src/backend/utils/mmgr/aset.c

433 lines
10 KiB
C

/*-------------------------------------------------------------------------
*
* aset.c
* Allocation set definitions.
*
* Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/mmgr/aset.c,v 1.20 1999/07/17 20:18:13 momjian 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
* 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
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/memutils.h"
#undef AllocSetReset
#undef malloc
#undef free
#undef realloc
/*--------------------
* 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.
*
* CAUTION: ALLOC_MINBITS must be large enough so that
* 1<<ALLOC_MINBITS is at least MAXALIGN,
* or we may fail to align the smallest chunks adequately.
* 16-byte alignment is enough on all currently known machines.
*--------------------
*/
#define ALLOC_MINBITS 4 /* smallest chunk size is 16 bytes */
#define ALLOC_SMALLCHUNK_LIMIT (1 << (ALLOCSET_NUM_FREELISTS-2+ALLOC_MINBITS))
/* Size of largest chunk that we use a fixed size for */
/*--------------------
* 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".
*
* Blocks allocated to hold oversize chunks do not follow this rule, however;
* they are just however big they need to be.
*--------------------
*/
#define ALLOC_MIN_BLOCK_SIZE 8192
#define ALLOC_MAX_BLOCK_SIZE (8 * 1024 * 1024)
#define ALLOC_BLOCKHDRSZ MAXALIGN(sizeof(AllocBlockData))
#define ALLOC_CHUNKHDRSZ MAXALIGN(sizeof(AllocChunkData))
#define AllocPointerGetChunk(ptr) \
((AllocChunk)(((char *)(ptr)) - ALLOC_CHUNKHDRSZ))
#define AllocChunkGetPointer(chk) \
((AllocPointer)(((char *)(chk)) + ALLOC_CHUNKHDRSZ))
#define AllocPointerGetAset(ptr) ((AllocSet)(AllocPointerGetChunk(ptr)->aset))
#define AllocPointerGetSize(ptr) (AllocPointerGetChunk(ptr)->size)
/* ----------
* AllocSetFreeIndex -
*
* Depending on the size of an allocation compute which freechunk
* list of the alloc set it belongs to.
* ----------
*/
static inline int
AllocSetFreeIndex(Size size)
{
int idx = 0;
if (size > 0)
{
size = (size - 1) >> ALLOC_MINBITS;
while (size != 0 && idx < ALLOCSET_NUM_FREELISTS - 1)
{
idx++;
size >>= 1;
}
}
return idx;
}
/*
* Public routines
*/
/*
* AllocPointerIsValid(pointer)
* AllocSetIsValid(set)
*
* .. are now macros in aset.h -cim 4/27/91
*/
/*
* AllocSetInit
* Initializes given allocation set.
*
* Note:
* The semantics of the mode are explained above. Limit is ignored
* for dynamic and static modes.
*
* Exceptions:
* BadArg if set is invalid pointer.
* BadArg if mode is invalid.
*/
void
AllocSetInit(AllocSet set, AllocMode mode, Size limit)
{
AssertArg(PointerIsValid(set));
AssertArg((int) DynamicAllocMode <= (int) mode);
AssertArg((int) mode <= (int) BoundedAllocMode);
/*
* XXX mode is currently ignored and treated as DynamicAllocMode. XXX
* limit is also ignored. This affects this whole file.
*/
memset(set, 0, sizeof(AllocSetData));
}
/*
* AllocSetReset
* Frees memory which is allocated in the given set.
*
* Exceptions:
* BadArg if set is invalid.
*/
void
AllocSetReset(AllocSet set)
{
AllocBlock block = set->blocks;
AllocBlock next;
AssertArg(AllocSetIsValid(set));
while (block != NULL)
{
next = block->next;
free(block);
block = next;
}
memset(set, 0, sizeof(AllocSetData));
}
/*
* AllocSetContains
* True iff allocation set contains given allocation element.
*
* Exceptions:
* BadArg if set is invalid.
* BadArg if pointer is invalid.
*/
bool
AllocSetContains(AllocSet set, AllocPointer pointer)
{
AssertArg(AllocSetIsValid(set));
AssertArg(AllocPointerIsValid(pointer));
return (AllocPointerGetAset(pointer) == set);
}
/*
* AllocSetAlloc
* Returns pointer to allocated memory of given size; memory is added
* to the set.
*
* Exceptions:
* BadArg if set is invalid.
* MemoryExhausted if allocation fails.
*/
AllocPointer
AllocSetAlloc(AllocSet set, Size size)
{
AllocBlock block;
AllocChunk chunk;
AllocChunk freeref = NULL;
int fidx;
Size chunk_size;
Size blksize;
AssertArg(AllocSetIsValid(set));
/*
* Lookup in the corresponding free list if there is a free chunk we
* could reuse
*
*/
fidx = AllocSetFreeIndex(size);
for (chunk = set->freelist[fidx]; chunk; chunk = (AllocChunk) chunk->aset)
{
if (chunk->size >= size)
break;
freeref = chunk;
}
/*
* If one is found, remove it from the free list, make it again a
* member of the alloc set and return it's data address.
*
*/
if (chunk != NULL)
{
if (freeref == NULL)
set->freelist[fidx] = (AllocChunk) chunk->aset;
else
freeref->aset = chunk->aset;
chunk->aset = (void *) set;
return AllocChunkGetPointer(chunk);
}
/*
* Choose the actual chunk size to allocate.
*/
if (size > ALLOC_SMALLCHUNK_LIMIT)
chunk_size = MAXALIGN(size);
else
chunk_size = 1 << (fidx + ALLOC_MINBITS);
Assert(chunk_size >= size);
/*
* If there is enough room in the active allocation block, always
* allocate the chunk there.
*/
if ((block = set->blocks) != NULL)
{
Size have_free = block->endptr - block->freeptr;
if (have_free < (chunk_size + ALLOC_CHUNKHDRSZ))
block = NULL;
}
/*
* Otherwise, if requested size exceeds smallchunk limit, allocate an
* entire separate block for this allocation
*
*/
if (block == NULL && size > ALLOC_SMALLCHUNK_LIMIT)
{
blksize = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
block = (AllocBlock) malloc(blksize);
if (block == NULL)
elog(FATAL, "Memory exhausted in AllocSetAlloc()");
block->aset = set;
block->freeptr = block->endptr = ((char *) block) + blksize;
chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ);
chunk->aset = set;
chunk->size = chunk_size;
/*
* Try to stick the block underneath the active allocation block,
* so that we don't lose the use of the space remaining therein.
*/
if (set->blocks != NULL)
{
block->next = set->blocks->next;
set->blocks->next = block;
}
else
{
block->next = NULL;
set->blocks = block;
}
return AllocChunkGetPointer(chunk);
}
/*
* Time to create a new regular block?
*/
if (block == NULL)
{
if (set->blocks == NULL)
{
blksize = ALLOC_MIN_BLOCK_SIZE;
block = (AllocBlock) malloc(blksize);
}
else
{
/* Get size of prior block */
blksize = set->blocks->endptr - ((char *) set->blocks);
/*
* Special case: if very first allocation was for a large
* chunk, could have a funny-sized top block. Do something
* reasonable.
*/
if (blksize < ALLOC_MIN_BLOCK_SIZE)
blksize = ALLOC_MIN_BLOCK_SIZE;
/* Crank it up, but not past max */
blksize <<= 1;
if (blksize > ALLOC_MAX_BLOCK_SIZE)
blksize = ALLOC_MAX_BLOCK_SIZE;
/* Try to allocate it */
block = (AllocBlock) malloc(blksize);
/*
* We could be asking for pretty big blocks here, so cope if
* malloc fails. But give up if there's less than a meg or so
* available...
*/
while (block == NULL && blksize > 1024 * 1024)
{
blksize >>= 1;
block = (AllocBlock) malloc(blksize);
}
}
if (block == NULL)
elog(FATAL, "Memory exhausted in AllocSetAlloc()");
block->aset = set;
block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
block->endptr = ((char *) block) + blksize;
block->next = set->blocks;
set->blocks = block;
}
/*
* OK, do the allocation
*/
chunk = (AllocChunk) (block->freeptr);
chunk->aset = (void *) set;
chunk->size = chunk_size;
block->freeptr += (chunk_size + ALLOC_CHUNKHDRSZ);
Assert(block->freeptr <= block->endptr);
return AllocChunkGetPointer(chunk);
}
/*
* AllocSetFree
* Frees allocated memory; memory is removed from the set.
*
* Exceptions:
* BadArg if set is invalid.
* BadArg if pointer is invalid.
* BadArg if pointer is not member of set.
*/
void
AllocSetFree(AllocSet set, AllocPointer pointer)
{
int fidx;
AllocChunk chunk;
/* AssertArg(AllocSetIsValid(set)); */
/* AssertArg(AllocPointerIsValid(pointer)); */
AssertArg(AllocSetContains(set, pointer));
chunk = AllocPointerGetChunk(pointer);
fidx = AllocSetFreeIndex(chunk->size);
chunk->aset = (void *) set->freelist[fidx];
set->freelist[fidx] = chunk;
}
/*
* AllocSetRealloc
* Returns new pointer to allocated memory of given size; this memory
* is added to the set. Memory associated with given pointer is copied
* into the new memory, and the old memory is freed.
*
* Exceptions:
* BadArg if set is invalid.
* BadArg if pointer is invalid.
* BadArg if pointer is not member of set.
* MemoryExhausted if allocation fails.
*/
AllocPointer
AllocSetRealloc(AllocSet set, AllocPointer pointer, Size size)
{
AllocPointer newPointer;
Size oldsize;
/* AssertArg(AllocSetIsValid(set)); */
/* AssertArg(AllocPointerIsValid(pointer)); */
AssertArg(AllocSetContains(set, pointer));
/*
* Chunk sizes are aligned to power of 2 on AllocSetAlloc(). Maybe the
* allocated area already is >= the new size.
*
*/
oldsize = AllocPointerGetSize(pointer);
if (oldsize >= size)
return pointer;
/* allocate new pointer */
newPointer = AllocSetAlloc(set, size);
/* fill new memory */
memmove(newPointer, pointer, (oldsize < size) ? oldsize : size);
/* free old pointer */
AllocSetFree(set, pointer);
return newPointer;
}
/*
* AllocSetDump
* Displays allocated set.
*/
void
AllocSetDump(AllocSet set)
{
elog(DEBUG, "Currently unable to dump AllocSet");
}