Alter AllocSet routines so that requests larger than
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 24 Aug 1999 20:11:19 +0000 (20:11 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 24 Aug 1999 20:11:19 +0000 (20:11 +0000)
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
src/include/utils/memutils.h

index 28f4417525d7b74b1504bdc9d93c1a33f6ef1359..7c7ba2828d94946549f49ab13cc37a7867bd063f 100644 (file)
@@ -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,
  * 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;
+       }
 }
 
 /*
index 7a3d12f60528f9952682f380e57a7bd1d8356169..cac5facc313da49c564207bf22be67211c75e611 100644 (file)
@@ -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.