Alter AllocSet routines so that requests larger than
authorTom Lane
Tue, 24 Aug 1999 20:11:19 +0000 (20:11 +0000)
committerTom Lane
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<
  * 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.