* 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))
* Public routines
*/
-/*
- * AllocPointerIsValid(pointer)
- * AllocSetIsValid(set)
- *
- * .. are now macros in aset.h -cim 4/27/91
- */
-
/*
* AllocSetInit
* Initializes given allocation set.
/*
* 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.
{
AllocBlock block;
AllocChunk chunk;
- AllocChunk freeref = NULL;
+ AllocChunk priorfree = NULL;
int fidx;
Size chunk_size;
Size blksize;
{
if (chunk->size >= size)
break;
- freeref = chunk;
+ priorfree = chunk;
}
/*
*/
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);
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)
{
}
/*
- * Time to create a new regular block?
+ * Time to create a new regular (multi-chunk) block?
*/
if (block == NULL)
{
void
AllocSetFree(AllocSet set, AllocPointer pointer)
{
- int fidx;
AllocChunk chunk;
/* AssertArg(AllocSetIsValid(set)); */
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;
+ }
}
/*
AllocPointer
AllocSetRealloc(AllocSet set, AllocPointer pointer, Size size)
{
- AllocPointer newPointer;
Size oldsize;
/* AssertArg(AllocSetIsValid(set)); */
/*
* 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;
+ }
}
/*