*
  * The memory context uses a very simple approach to free space management.
  * Instead of a complex global freelist, each block tracks a number
- * of allocated and freed chunks. Freed chunks are not reused, and once all
- * chunks in a block are freed, the whole block is thrown away. When the
- * chunks allocated in the same block have similar lifespan, this works
- * very well and is very cheap.
+ * of allocated and freed chunks.  The block is classed as empty when the
+ * number of free chunks is equal to the number of allocated chunks.  When
+ * this occurs, instead of freeing the block, we try to "recycle" it, i.e.
+ * reuse it for new allocations.  This is done by setting the block in the
+ * context's 'freeblock' field.  If the freeblock field is already occupied
+ * by another free block we simply return the newly empty block to malloc.
  *
- * The current implementation only uses a fixed block size - maybe it should
- * adapt a min/max block size range, and grow the blocks automatically.
- * It already uses dedicated blocks for oversized chunks.
- *
- * XXX It might be possible to improve this by keeping a small freelist for
- * only a small number of recent blocks, but it's not clear it's worth the
- * additional complexity.
+ * This approach to free blocks requires fewer malloc/free calls for truely
+ * first allocated, first free'd allocation patterns.
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "lib/ilist.h"
+#include "port/pg_bitutils.h"
 #include "utils/memdebug.h"
 #include "utils/memutils.h"
 
 #define Generation_BLOCKHDRSZ  MAXALIGN(sizeof(GenerationBlock))
 #define Generation_CHUNKHDRSZ  sizeof(GenerationChunk)
 
+#define Generation_CHUNK_FRACTION  8
+
 typedef struct GenerationBlock GenerationBlock; /* forward reference */
 typedef struct GenerationChunk GenerationChunk;
 
    MemoryContextData header;   /* Standard memory-context fields */
 
    /* Generational context parameters */
-   Size        blockSize;      /* standard block size */
-
-   GenerationBlock *block;     /* current (most recently allocated) block */
+   Size        initBlockSize;  /* initial block size */
+   Size        maxBlockSize;   /* maximum block size */
+   Size        nextBlockSize;  /* next block size to allocate */
+   Size        allocChunkLimit;    /* effective chunk size limit */
+
+   GenerationBlock *block;     /* current (most recently allocated) block, or
+                                * NULL if we've just freed the most recent
+                                * block */
+   GenerationBlock *freeblock; /* pointer to a block that's being recycled,
+                                * or NULL if there's no such block. */
+   GenerationBlock *keeper;    /* keep this block over resets */
    dlist_head  blocks;         /* list of blocks */
 } GenerationContext;
 
 /*
  * GenerationBlock
  *     GenerationBlock is the unit of memory that is obtained by generation.c
- *     from malloc().  It contains one or more GenerationChunks, which are
+ *     from malloc().  It contains zero or more GenerationChunks, which are
  *     the units requested by palloc() and freed by pfree().  GenerationChunks
  *     cannot be returned to malloc() individually, instead pfree()
  *     updates the free counter of the block and when all chunks in a block
- *     are free the whole block is returned to malloc().
+ *     are free the whole block can be returned to malloc().
  *
  *     GenerationBlock is the header data for a block --- the usable space
  *     within the block begins at the next alignment boundary.
 #define GenerationChunkGetPointer(chk) \
    ((GenerationPointer *)(((char *)(chk)) + Generation_CHUNKHDRSZ))
 
+/* Inlined helper functions */
+static inline void GenerationBlockInit(GenerationBlock *block, Size blksize);
+static inline bool GenerationBlockIsEmpty(GenerationBlock *block);
+static inline void GenerationBlockMarkEmpty(GenerationBlock *block);
+static inline Size GenerationBlockFreeBytes(GenerationBlock *block);
+static inline void GenerationBlockFree(GenerationContext *set,
+                                      GenerationBlock *block);
+
 /*
  * These functions implement the MemoryContext API for Generation contexts.
  */
  *
  * parent: parent context, or NULL if top-level context
  * name: name of context (must be statically allocated)
- * blockSize: generation block size
+ * minContextSize: minimum context size
+ * initBlockSize: initial allocation block size
+ * maxBlockSize: maximum allocation block size
  */
 MemoryContext
 GenerationContextCreate(MemoryContext parent,
                        const char *name,
-                       Size blockSize)
+                       Size minContextSize,
+                       Size initBlockSize,
+                       Size maxBlockSize)
 {
+   Size        firstBlockSize;
+   Size        allocSize;
    GenerationContext *set;
+   GenerationBlock *block;
 
    /* Assert we padded GenerationChunk properly */
    StaticAssertStmt(Generation_CHUNKHDRSZ == MAXALIGN(Generation_CHUNKHDRSZ),
                     "padding calculation in GenerationChunk is wrong");
 
    /*
-    * First, validate allocation parameters.  (If we're going to throw an
-    * error, we should do so before the context is created, not after.)  We
-    * somewhat arbitrarily enforce a minimum 1K block size, mostly because
-    * that's what AllocSet does.
+    * First, validate allocation parameters.  Asserts seem sufficient because
+    * nobody varies their parameters at runtime.  We somewhat arbitrarily
+    * enforce a minimum 1K block size.
     */
-   if (blockSize != MAXALIGN(blockSize) ||
-       blockSize < 1024 ||
-       !AllocHugeSizeIsValid(blockSize))
-       elog(ERROR, "invalid blockSize for memory context: %zu",
-            blockSize);
+   Assert(initBlockSize == MAXALIGN(initBlockSize) &&
+          initBlockSize >= 1024);
+   Assert(maxBlockSize == MAXALIGN(maxBlockSize) &&
+          maxBlockSize >= initBlockSize &&
+          AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */
+   Assert(minContextSize == 0 ||
+          (minContextSize == MAXALIGN(minContextSize) &&
+           minContextSize >= 1024 &&
+           minContextSize <= maxBlockSize));
+
+   /* Determine size of initial block */
+   allocSize = MAXALIGN(sizeof(GenerationContext)) +
+       Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ;
+   if (minContextSize != 0)
+       allocSize = Max(allocSize, minContextSize);
+   else
+       allocSize = Max(allocSize, initBlockSize);
 
    /*
-    * Allocate the context header.  Unlike aset.c, we never try to combine
-    * this with the first regular block, since that would prevent us from
-    * freeing the first generation of allocations.
+    * Allocate the initial block.  Unlike other generation.c blocks, it
+    * starts with the context header and its block header follows that.
     */
-
-   set = (GenerationContext *) malloc(MAXALIGN(sizeof(GenerationContext)));
+   set = (GenerationContext *) malloc(allocSize);
    if (set == NULL)
    {
        MemoryContextStats(TopMemoryContext);
     * Avoid writing code that can fail between here and MemoryContextCreate;
     * we'd leak the header if we ereport in this stretch.
     */
+   dlist_init(&set->blocks);
+
+   /* Fill in the initial block's block header */
+   block = (GenerationBlock *) (((char *) set) + MAXALIGN(sizeof(GenerationContext)));
+   /* determine the block size and initialize it */
+   firstBlockSize = allocSize - MAXALIGN(sizeof(GenerationContext));
+   GenerationBlockInit(block, firstBlockSize);
+
+   /* add it to the doubly-linked list of blocks */
+   dlist_push_head(&set->blocks, &block->node);
+
+   /* use it as the current allocation block */
+   set->block = block;
+
+   /* No free block, yet */
+   set->freeblock = NULL;
+
+   /* Mark block as not to be released at reset time */
+   set->keeper = block;
 
    /* Fill in GenerationContext-specific header fields */
-   set->blockSize = blockSize;
-   set->block = NULL;
-   dlist_init(&set->blocks);
+   set->initBlockSize = initBlockSize;
+   set->maxBlockSize = maxBlockSize;
+   set->nextBlockSize = initBlockSize;
+
+   /*
+    * Compute the allocation chunk size limit for this context.
+    *
+    * Follows similar ideas as AllocSet, see aset.c for details ...
+    */
+   set->allocChunkLimit = maxBlockSize;
+   while ((Size) (set->allocChunkLimit + Generation_CHUNKHDRSZ) >
+          (Size) ((Size) (maxBlockSize - Generation_BLOCKHDRSZ) / Generation_CHUNK_FRACTION))
+       set->allocChunkLimit >>= 1;
 
    /* Finally, do the type-independent part of context creation */
    MemoryContextCreate((MemoryContext) set,
                        parent,
                        name);
 
+   ((MemoryContext) set)->mem_allocated = firstBlockSize;
+
    return (MemoryContext) set;
 }
 
    GenerationCheck(context);
 #endif
 
+   /*
+    * NULLify the free block pointer.  We must do this before calling
+    * GenerationBlockFree as that function never expects to free the
+    * freeblock.
+    */
+   set->freeblock = NULL;
+
    dlist_foreach_modify(miter, &set->blocks)
    {
        GenerationBlock *block = dlist_container(GenerationBlock, node, miter.cur);
 
-       dlist_delete(miter.cur);
-
-       context->mem_allocated -= block->blksize;
-
-#ifdef CLOBBER_FREED_MEMORY
-       wipe_mem(block, block->blksize);
-#endif
-
-       free(block);
+       if (block == set->keeper)
+           GenerationBlockMarkEmpty(block);
+       else
+           GenerationBlockFree(set, block);
    }
 
-   set->block = NULL;
+   /* set it so new allocations to make use of the keeper block */
+   set->block = set->keeper;
+
+   /* Reset block size allocation sequence, too */
+   set->nextBlockSize = set->initBlockSize;
 
-   Assert(dlist_is_empty(&set->blocks));
+   /* Ensure there is only 1 item in the dlist */
+   Assert(!dlist_is_empty(&set->blocks));
+   Assert(!dlist_has_next(&set->blocks, dlist_head_node(&set->blocks)));
 }
 
 /*
 static void
 GenerationDelete(MemoryContext context)
 {
-   /* Reset to release all the GenerationBlocks */
+   /* Reset to release all releasable GenerationBlocks */
    GenerationReset(context);
-   /* And free the context header */
+   /* And free the context header and keeper block */
    free(context);
 }
 
    GenerationBlock *block;
    GenerationChunk *chunk;
    Size        chunk_size = MAXALIGN(size);
+   Size        required_size = chunk_size + Generation_CHUNKHDRSZ;
 
    /* is it an over-sized chunk? if yes, allocate special block */
-   if (chunk_size > set->blockSize / 8)
+   if (chunk_size > set->allocChunkLimit)
    {
-       Size        blksize = chunk_size + Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ;
+       Size        blksize = required_size + Generation_BLOCKHDRSZ;
 
        block = (GenerationBlock *) malloc(blksize);
        if (block == NULL)
    }
 
    /*
-    * Not an over-sized chunk. Is there enough space in the current block? If
-    * not, allocate a new "regular" block.
+    * Not an oversized chunk.  We try to first make use of the current block,
+    * but if there's not enough space in it, instead of allocating a new
+    * block, we look to see if the freeblock is empty and has enough space.
+    * If not, we'll also try the same using the keeper block.  The keeper
+    * block may have become empty and we have no other way to reuse it again
+    * if we don't try to use it explicitly here.
+    *
+    * We don't want to start filling the freeblock before the current block
+    * is full, otherwise we may cause fragmentation in FIFO type workloads.
+    * We only switch to using the freeblock or keeper block if those blocks
+    * are completely empty.  If we didn't do that we could end up fragmenting
+    * consecutive allocations over multiple blocks which would be a problem
+    * that would compound over time.
     */
    block = set->block;
 
-   if ((block == NULL) ||
-       (block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size)
+   if (block == NULL ||
+       GenerationBlockFreeBytes(block) < required_size)
    {
-       Size        blksize = set->blockSize;
+       Size        blksize;
+       GenerationBlock *freeblock = set->freeblock;
 
-       block = (GenerationBlock *) malloc(blksize);
+       if (freeblock != NULL &&
+           GenerationBlockIsEmpty(freeblock) &&
+           GenerationBlockFreeBytes(freeblock) >= required_size)
+       {
+           block = freeblock;
 
-       if (block == NULL)
-           return NULL;
+           /*
+            * Zero out the freeblock as we'll set this to the current block
+            * below
+            */
+           set->freeblock = NULL;
+       }
+       else if (GenerationBlockIsEmpty(set->keeper) &&
+                GenerationBlockFreeBytes(set->keeper) >= required_size)
+       {
+           block = set->keeper;
+       }
+       else
+       {
+           /*
+            * The first such block has size initBlockSize, and we double the
+            * space in each succeeding block, but not more than maxBlockSize.
+            */
+           blksize = set->nextBlockSize;
+           set->nextBlockSize <<= 1;
+           if (set->nextBlockSize > set->maxBlockSize)
+               set->nextBlockSize = set->maxBlockSize;
 
-       context->mem_allocated += blksize;
+           /* we'll need a block hdr too, so add that to the required size */
+           required_size += Generation_BLOCKHDRSZ;
 
-       block->blksize = blksize;
-       block->nchunks = 0;
-       block->nfree = 0;
+           /* round the size up to the next power of 2 */
+           if (blksize < required_size)
+               blksize = pg_nextpower2_size_t(required_size);
 
-       block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
-       block->endptr = ((char *) block) + blksize;
+           block = (GenerationBlock *) malloc(blksize);
 
-       /* Mark unallocated space NOACCESS. */
-       VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
-                                  blksize - Generation_BLOCKHDRSZ);
+           if (block == NULL)
+               return NULL;
 
-       /* add it to the doubly-linked list of blocks */
-       dlist_push_head(&set->blocks, &block->node);
+           context->mem_allocated += blksize;
+
+           /* initialize the new block */
+           GenerationBlockInit(block, blksize);
+
+           /* add it to the doubly-linked list of blocks */
+           dlist_push_head(&set->blocks, &block->node);
+
+           /* Zero out the freeblock in case it's become full */
+           set->freeblock = NULL;
+       }
 
        /* and also use it as the current allocation block */
        set->block = block;
    return GenerationChunkGetPointer(chunk);
 }
 
+/*
+ * GenerationBlockInit
+ *     Initializes 'block' assuming 'blksize'.  Does not update the context's
+ *     mem_allocated field.
+ */
+static inline void
+GenerationBlockInit(GenerationBlock *block, Size blksize)
+{
+   block->blksize = blksize;
+   block->nchunks = 0;
+   block->nfree = 0;
+
+   block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
+   block->endptr = ((char *) block) + blksize;
+
+   /* Mark unallocated space NOACCESS. */
+   VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
+                              blksize - Generation_BLOCKHDRSZ);
+}
+
+/*
+ * GenerationBlockIsEmpty
+ *     Returns true iif 'block' contains no chunks
+ */
+static inline bool
+GenerationBlockIsEmpty(GenerationBlock *block)
+{
+   return (block->nchunks == 0);
+}
+
+/*
+ * GenerationBlockMarkEmpty
+ *     Set a block as empty.  Does not free the block.
+ */
+static inline void
+GenerationBlockMarkEmpty(GenerationBlock *block)
+{
+#if defined(USE_VALGRIND) || defined(CLOBBER_FREED_MEMORY)
+   char       *datastart = ((char *) block) + Generation_BLOCKHDRSZ;
+#endif
+
+#ifdef CLOBBER_FREED_MEMORY
+   wipe_mem(datastart, block->freeptr - datastart);
+#else
+   /* wipe_mem() would have done this */
+   VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
+#endif
+
+   /* Reset the block, but don't return it to malloc */
+   block->nchunks = 0;
+   block->nfree = 0;
+   block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
+
+}
+
+/*
+ * GenerationBlockFreeBytes
+ *     Returns the number of bytes free in 'block'
+ */
+static inline Size
+GenerationBlockFreeBytes(GenerationBlock *block)
+{
+   return (block->endptr - block->freeptr);
+}
+
+/*
+ * GenerationBlockFree
+ *     Remove 'block' from 'set' and release the memory consumed by it.
+ */
+static inline void
+GenerationBlockFree(GenerationContext *set, GenerationBlock *block)
+{
+   /* Make sure nobody tries to free the keeper block */
+   Assert(block != set->keeper);
+   /* We shouldn't be freeing the freeblock either */
+   Assert(block != set->freeblock);
+
+   /* release the block from the list of blocks */
+   dlist_delete(&block->node);
+
+   ((MemoryContext) set)->mem_allocated -= block->blksize;
+
+#ifdef CLOBBER_FREED_MEMORY
+   wipe_mem(block, block->blksize);
+#endif
+
+   free(block);
+}
+
 /*
  * GenerationFree
  *     Update number of chunks in the block, and if all chunks in the block
    if (block->nfree < block->nchunks)
        return;
 
+   /* Don't try to free the keeper block, just mark it empty */
+   if (block == set->keeper)
+   {
+       GenerationBlockMarkEmpty(block);
+       return;
+   }
+
    /*
-    * The block is empty, so let's get rid of it. First remove it from the
-    * list of blocks, then return it to malloc().
+    * If there is no freeblock set or if this is the freeblock then instead
+    * of freeing this memory, we keep it around so that new allocations have
+    * the option of recycling it.
     */
-   dlist_delete(&block->node);
+   if (set->freeblock == NULL || set->freeblock == block)
+   {
+       /* XXX should we only recycle maxBlockSize sized blocks? */
+       set->freeblock = block;
+       GenerationBlockMarkEmpty(block);
+       return;
+   }
 
    /* Also make sure the block is not marked as the current block. */
    if (set->block == block)
        set->block = NULL;
 
+   /*
+    * The block is empty, so let's get rid of it. First remove it from the
+    * list of blocks, then return it to malloc().
+    */
+   dlist_delete(&block->node);
+
    context->mem_allocated -= block->blksize;
    free(block);
 }
 GenerationIsEmpty(MemoryContext context)
 {
    GenerationContext *set = (GenerationContext *) context;
+   dlist_iter  iter;
+
+   dlist_foreach(iter, &set->blocks)
+   {
+       GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
+
+       if (block->nchunks > 0)
+           return false;
+   }
 
-   return dlist_is_empty(&set->blocks);
+   return true;
 }
 
 /*
        total_allocated += block->blksize;
 
        /*
-        * nfree > nchunks is surely wrong, and we don't expect to see
-        * equality either, because such a block should have gotten freed.
+        * nfree > nchunks is surely wrong.  Equality is allowed as the block
+        * might completely empty if it's the freeblock.
         */
-       if (block->nfree >= block->nchunks)
+       if (block->nfree > block->nchunks)
            elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
                 name, block->nfree, block, block->nchunks);