From 2b67dc53874029219cf7905ceab520b75204e552 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 24 Aug 1999 20:11:19 +0000
Subject: [PATCH] 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?

---
 src/backend/utils/mmgr/aset.c | 173 ++++++++++++++++++++++++++--------
 src/include/utils/memutils.h  |  67 +++++++------
 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))
@@ -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 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.
-- 
GitLab