Modify aset.c logic so that blocks requested from malloc get
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 22 May 1999 23:19:37 +0000 (23:19 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 22 May 1999 23:19:37 +0000 (23:19 +0000)
bigger the more space is used in an allocset.  This reduces the malloc
overhead very substantially on queries that need lots of memory.

src/backend/utils/mmgr/aset.c
src/include/utils/memutils.h

index de364bcf90addf3b969f72bfaf0478a46495eb92..04bdba93292591719d28de8a69cc26cc8eff7300 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/mmgr/aset.c,v 1.14 1999/02/13 23:20:09 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/mmgr/aset.c,v 1.15 1999/05/22 23:19:37 tgl Exp $
  *
  * NOTE:
  * This is a new (Feb. 05, 1999) implementation of the allocation set
@@ -16,7 +16,7 @@
  * 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
- * on AllocSetReset() at once, what happens when the memory context gets
+ * at once on AllocSetReset(), which happens when the memory context gets
  * destroyed.
  *             Jan Wieck
  *-------------------------------------------------------------------------
 #undef realloc
 
 
-#define    ALLOC_BLOCK_SIZE    8192
-#define    ALLOC_CHUNK_LIMIT   512
+/*--------------------
+ * 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))
@@ -65,11 +93,14 @@ AllocSetFreeIndex(Size size)
 {
    int idx = 0;
 
-   size = (size - 1) >> 4;
-   while (size != 0 && idx < 7)
+   if (size > 0)
    {
-       idx++;
-       size >>= 1;
+       size = (size - 1) >> ALLOC_MINBITS;
+       while (size != 0 && idx < ALLOCSET_NUM_FREELISTS-1)
+       {
+           idx++;
+           size >>= 1;
+       }
    }
 
    return idx;
@@ -174,6 +205,7 @@ AllocSetAlloc(AllocSet set, Size size)
    AllocChunk      freeref = NULL;
    int             fidx;
    Size            chunk_size;
+   Size            blksize;
 
    AssertArg(AllocSetIsValid(set));
 
@@ -191,7 +223,7 @@ AllocSetAlloc(AllocSet set, Size size)
    }
 
    /*
-    * If found, remove it from the free list, make it again
+    * 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.
     *
     */
@@ -207,26 +239,49 @@ AllocSetAlloc(AllocSet set, Size size)
    }
 
    /*
-    * If requested size exceeds smallchunk limit, allocate a separate,
-    * entire used block for this allocation
-    *
+    * Choose the actual chunk size to allocate.
     */
-   if (size > ALLOC_CHUNK_LIMIT)
+   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    blksize;
+       Size        have_free = block->endptr - block->freeptr;
 
-       chunk_size = MAXALIGN(size);
+       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) + ALLOC_BLOCKHDRSZ;
+       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;
@@ -241,33 +296,61 @@ AllocSetAlloc(AllocSet set, Size size)
        return AllocChunkGetPointer(chunk);
    }
 
-   chunk_size = 16 << fidx;
-
-   if ((block = set->blocks) != NULL)
-   {
-       Size        have_free = block->endptr - block->freeptr;
-
-       if (have_free < (chunk_size + ALLOC_CHUNKHDRSZ))
-           block = NULL;
-   }
-
+   /*
+    * Time to create a new regular block?
+    */
    if (block == NULL)
    {
-       block = (AllocBlock) malloc(ALLOC_BLOCK_SIZE);
+       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->next = set->blocks;
        block->freeptr = ((char *)block) + ALLOC_BLOCKHDRSZ;
-       block->endptr = ((char *)block) + ALLOC_BLOCK_SIZE;
+       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);
 }
@@ -325,14 +408,14 @@ AllocSetRealloc(AllocSet set, AllocPointer pointer, Size size)
     * Maybe the allocated area already is >= the new size.
     *
     */
-   if (AllocPointerGetSize(pointer) >= size)
+   oldsize = AllocPointerGetSize(pointer);
+   if (oldsize >= size)
        return pointer;
 
    /* allocate new pointer */
    newPointer = AllocSetAlloc(set, size);
 
    /* fill new memory */
-   oldsize = AllocPointerGetSize(pointer);
    memmove(newPointer, pointer, (oldsize < size) ? oldsize : size);
 
    /* free old pointer */
index 009d086311c0e6e94b753e93eb6455007f92091b..e95aec57c9014409c7d2fbadf820cc03f55e05a6 100644 (file)
@@ -15,7 +15,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: memutils.h,v 1.23 1999/03/25 19:05:19 tgl Exp $
+ * $Id: memutils.h,v 1.24 1999/05/22 23:19:36 tgl Exp $
  *
  * NOTES
  *   some of the information in this file will be moved to
@@ -96,7 +96,7 @@ extern void OrderedElemPushInto(OrderedElem elem, OrderedSet Set);
  *     an allocation is requested for a set, memory is allocated and a
  *     pointer is returned.  Subsequently, this memory may be freed or
  *     reallocated.  In addition, an allocation set may be reset which
- *     will cause all allocated memory to be freed.
+ *     will cause all memory allocated within it to be freed.
  *
  *     Allocations may occur in four different modes.  The mode of
  *     allocation does not affect the behavior of allocations except in
@@ -109,7 +109,7 @@ extern void OrderedElemPushInto(OrderedElem elem, OrderedSet Set);
  *     and freed very frequently.  This is a good choice when allocation
  *     characteristics are unknown.  This is the default mode.
  *
- *     "Static" mode attemts to allocate space as efficiently as possible
+ *     "Static" mode attempts to allocate space as efficiently as possible
  *     without regard to freeing memory.  This mode should be chosen only
  *     when it is known that many allocations will occur but that very
  *     little of the allocated memory will be explicitly freed.
@@ -129,7 +129,7 @@ extern void OrderedElemPushInto(OrderedElem elem, OrderedSet Set);
  *     Allocation sets are not automatically reset on a system reset.
  *     Higher level code is responsible for cleaning up.
  *
- *     There may other modes in the future.
+ *     There may be other modes in the future.
  */
 
 /*
@@ -176,7 +176,9 @@ typedef AllocBlockData *AllocBlock;
  *     The prefix of each piece of memory in an AllocBlock
  */
 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                        size;
 } AllocChunkData;
 
@@ -189,7 +191,8 @@ typedef AllocChunkData *AllocChunk;
 typedef struct AllocSetData
 {
    struct AllocBlockData       *blocks;
-   struct AllocChunkData       *freelist[8];
+#define ALLOCSET_NUM_FREELISTS 8
+   struct AllocChunkData       *freelist[ALLOCSET_NUM_FREELISTS];
    /* Note: this will change in the future to support other modes */
 } AllocSetData;