Introduce a bump memory allocator
authorDavid Rowley <drowley@postgresql.org>
Sun, 7 Apr 2024 12:02:43 +0000 (00:02 +1200)
committerDavid Rowley <drowley@postgresql.org>
Sun, 7 Apr 2024 12:02:43 +0000 (00:02 +1200)
This introduces a bump MemoryContext type.  The bump context is best
suited for short-lived memory contexts which require only allocations
of memory and never a pfree or repalloc, which are unsupported.

Memory palloc'd into a bump context has no chunk header.  This makes
bump a useful context type when lots of small allocations need to be
done without any need to pfree those allocations.  Allocation sizes are
rounded up to the next MAXALIGN boundary, so with this and no chunk
header, allocations are very compact indeed.

Allocations are also very fast as bump does not check any freelists to
try and make use of previously free'd chunks.  It just checks if there
is enough room on the current block, and if so it bumps the freeptr
beyond this chunk and returns the value that the freeptr was previously
pointing to.  Simple and fast.  A new block is malloc'd when there's not
enough space in the current block.

Code using the bump allocator must take care never to call any functions
which could try to call realloc() (or variants), pfree(),
GetMemoryChunkContext() or GetMemoryChunkSpace() on a bump allocated
chunk.  Due to lack of chunk headers, these operations are unsupported.
To increase the chances of catching such issues, when compiled with
MEMORY_CONTEXT_CHECKING, bump allocated chunks are given a header and
any attempt to perform an unsupported operation will result in an ERROR.
Without MEMORY_CONTEXT_CHECKING, code attempting an unsupported
operation could result in a segfault.

A follow-on commit will implement the first user of bump.

Author: David Rowley
Reviewed-by: Nathan Bossart
Reviewed-by: Matthias van de Meent
Reviewed-by: Tomas Vondra
Reviewed-by: John Naylor
Discussion: https://postgr.es/m/CAApHDvqGSpCU95TmM=Bp=6xjL_nLys4zdZOpfNyWBk97Xrdj2w@mail.gmail.com

src/backend/nodes/gen_node_support.pl
src/backend/utils/mmgr/Makefile
src/backend/utils/mmgr/bump.c [new file with mode: 0644]
src/backend/utils/mmgr/mcxt.c
src/backend/utils/mmgr/meson.build
src/include/nodes/memnodes.h
src/include/utils/memutils.h
src/include/utils/memutils_internal.h
src/tools/pgindent/typedefs.list

index d4244facbb3ba8370cd6f352ff2586aae077609a..81df3bdf95f017c49fd5d3b63f4217e1985565aa 100644 (file)
@@ -149,7 +149,7 @@ my @abstract_types = qw(Node);
 # they otherwise don't participate in node support.
 my @extra_tags = qw(
   IntList OidList XidList
-  AllocSetContext GenerationContext SlabContext
+  AllocSetContext GenerationContext SlabContext BumpContext
   TIDBitmap
   WindowObjectData
 );
index dae3432c98af59757142003feecbbfe4f68471fc..01a1fb8527022ab266aef38f34ecadd97db63ca2 100644 (file)
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
 OBJS = \
    alignedalloc.o \
    aset.o \
+   bump.o \
    dsa.o \
    freepage.o \
    generation.o \
diff --git a/src/backend/utils/mmgr/bump.c b/src/backend/utils/mmgr/bump.c
new file mode 100644 (file)
index 0000000..a17d186
--- /dev/null
@@ -0,0 +1,811 @@
+/*-------------------------------------------------------------------------
+ *
+ * bump.c
+ *   Bump allocator definitions.
+ *
+ * Bump is a MemoryContext implementation designed for memory usages which
+ * require allocating a large number of chunks, none of which ever need to be
+ * pfree'd or realloc'd.  Chunks allocated by this context have no chunk header
+ * and operations which ordinarily require looking at the chunk header cannot
+ * be performed.  For example, pfree, realloc, GetMemoryChunkSpace and
+ * GetMemoryChunkContext are all not possible with bump allocated chunks.  The
+ * only way to release memory allocated by this context type is to reset or
+ * delete the context.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *   src/backend/utils/mmgr/bump.c
+ *
+ *
+ * Bump is best suited to cases which require a large number of short-lived
+ * chunks where performance matters.  Because bump allocated chunks don't
+ * have a chunk header, it can fit more chunks on each block.  This means we
+ * can do more with less memory and fewer cache lines.  The reason it's best
+ * suited for short-lived usages of memory is that ideally, pointers to bump
+ * allocated chunks won't be visible to a large amount of code.  The more
+ * code that operates on memory allocated by this allocator, the more chances
+ * that some code will try to perform a pfree or one of the other operations
+ * which are made impossible due to the lack of chunk header.  In order to
+ * detect accidental usage of the various disallowed operations, we do add a
+ * MemoryChunk chunk header in MEMORY_CONTEXT_CHECKING builds and have the
+ * various disallowed functions raise an ERROR.
+ *
+ * Allocations are MAXALIGNed.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/ilist.h"
+#include "port/pg_bitutils.h"
+#include "utils/memdebug.h"
+#include "utils/memutils.h"
+#include "utils/memutils_memorychunk.h"
+#include "utils/memutils_internal.h"
+
+#define Bump_BLOCKHDRSZ    MAXALIGN(sizeof(BumpBlock))
+
+/* No chunk header unless built with MEMORY_CONTEXT_CHECKING */
+#ifdef MEMORY_CONTEXT_CHECKING
+#define Bump_CHUNKHDRSZ    sizeof(MemoryChunk)
+#else
+#define Bump_CHUNKHDRSZ    0
+#endif
+
+#define Bump_CHUNK_FRACTION    8
+
+/* The keeper block is allocated in the same allocation as the set */
+#define KeeperBlock(set) ((BumpBlock *) ((char *) (set) + sizeof(BumpContext)))
+#define IsKeeperBlock(set, blk) (KeeperBlock(set) == (blk))
+
+typedef struct BumpBlock BumpBlock; /* forward reference */
+
+typedef struct BumpContext
+{
+   MemoryContextData header;   /* Standard memory-context fields */
+
+   /* Bump context parameters */
+   uint32      initBlockSize;  /* initial block size */
+   uint32      maxBlockSize;   /* maximum block size */
+   uint32      nextBlockSize;  /* next block size to allocate */
+   uint32      allocChunkLimit;    /* effective chunk size limit */
+
+   dlist_head  blocks;         /* list of blocks with the block currently
+                                * being filled at the head */
+} BumpContext;
+
+/*
+ * BumpBlock
+ *     BumpBlock is the unit of memory that is obtained by bump.c from
+ *     malloc().  It contains zero or more allocations, which are the
+ *     units requested by palloc().
+ */
+struct BumpBlock
+{
+   dlist_node  node;           /* doubly-linked list of blocks */
+#ifdef MEMORY_CONTEXT_CHECKING
+   BumpContext *context;       /* pointer back to the owning context */
+#endif
+   char       *freeptr;        /* start of free space in this block */
+   char       *endptr;         /* end of space in this block */
+};
+
+/*
+ * BumpIsValid
+ *     True iff set is valid bump context.
+ */
+#define BumpIsValid(set) \
+   (PointerIsValid(set) && IsA(set, BumpContext))
+
+/*
+ * BumpBlockIsValid
+ *     True iff block is valid block of a bump context
+ */
+#define BumpBlockIsValid(block) \
+   (PointerIsValid(block) && BumpIsValid((block)->context))
+
+/*
+ * We always store external chunks on a dedicated block.  This makes fetching
+ * the block from an external chunk easy since it's always the first and only
+ * chunk on the block.
+ */
+#define ExternalChunkGetBlock(chunk) \
+   (BumpBlock *) ((char *) chunk - Bump_BLOCKHDRSZ)
+
+/* Inlined helper functions */
+static inline void BumpBlockInit(BumpContext *context, BumpBlock *block,
+                                Size blksize);
+static inline bool BumpBlockIsEmpty(BumpBlock *block);
+static inline void BumpBlockMarkEmpty(BumpBlock *block);
+static inline Size BumpBlockFreeBytes(BumpBlock *block);
+static inline void BumpBlockFree(BumpContext *set, BumpBlock *block);
+
+
+/*
+* BumpContextCreate
+*      Create a new Bump context.
+*
+* parent: parent context, or NULL if top-level context
+* name: name of context (must be statically allocated)
+* minContextSize: minimum context size
+* initBlockSize: initial allocation block size
+* maxBlockSize: maximum allocation block size
+*/
+MemoryContext
+BumpContextCreate(MemoryContext parent, const char *name, Size minContextSize,
+                 Size initBlockSize, Size maxBlockSize)
+{
+   Size        firstBlockSize;
+   Size        allocSize;
+   BumpContext *set;
+   BumpBlock  *block;
+
+   /* ensure MemoryChunk's size is properly maxaligned */
+   StaticAssertDecl(Bump_CHUNKHDRSZ == MAXALIGN(Bump_CHUNKHDRSZ),
+                    "sizeof(MemoryChunk) is not maxaligned");
+
+   /*
+    * First, validate allocation parameters.  Asserts seem sufficient because
+    * nobody varies their parameters at runtime.  We somewhat arbitrarily
+    * enforce a minimum 1K block size.  We restrict the maximum block size to
+    * MEMORYCHUNK_MAX_BLOCKOFFSET as MemoryChunks are limited to this in
+    * regards to addressing the offset between the chunk and the block that
+    * the chunk is stored on.  We would be unable to store the offset between
+    * the chunk and block for any chunks that were beyond
+    * MEMORYCHUNK_MAX_BLOCKOFFSET bytes into the block if the block was to be
+    * larger than this.
+    */
+   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));
+   Assert(maxBlockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET);
+
+   /* Determine size of initial block */
+   allocSize = MAXALIGN(sizeof(BumpContext)) + Bump_BLOCKHDRSZ +
+       Bump_CHUNKHDRSZ;
+   if (minContextSize != 0)
+       allocSize = Max(allocSize, minContextSize);
+   else
+       allocSize = Max(allocSize, initBlockSize);
+
+   /*
+    * Allocate the initial block.  Unlike other bump.c blocks, it starts with
+    * the context header and its block header follows that.
+    */
+   set = (BumpContext *) malloc(allocSize);
+   if (set == NULL)
+   {
+       MemoryContextStats(TopMemoryContext);
+       ereport(ERROR,
+               (errcode(ERRCODE_OUT_OF_MEMORY),
+                errmsg("out of memory"),
+                errdetail("Failed while creating memory context \"%s\".",
+                          name)));
+   }
+
+   /*
+    * Avoid writing code that can fail between here and MemoryContextCreate;
+    * we'd leak the header and initial block if we ereport in this stretch.
+    */
+   dlist_init(&set->blocks);
+
+   /* Fill in the initial block's block header */
+   block = (BumpBlock *) (((char *) set) + MAXALIGN(sizeof(BumpContext)));
+   /* determine the block size and initialize it */
+   firstBlockSize = allocSize - MAXALIGN(sizeof(BumpContext));
+   BumpBlockInit(set, block, firstBlockSize);
+
+   /* add it to the doubly-linked list of blocks */
+   dlist_push_head(&set->blocks, &block->node);
+
+   /*
+    * Fill in BumpContext-specific header fields.  The Asserts above should
+    * ensure that these all fit inside a uint32.
+    */
+   set->initBlockSize = (uint32) initBlockSize;
+   set->maxBlockSize = (uint32) maxBlockSize;
+   set->nextBlockSize = (uint32) initBlockSize;
+
+   /*
+    * Compute the allocation chunk size limit for this context.
+    *
+    * Limit the maximum size a non-dedicated chunk can be so that we can fit
+    * at least Bump_CHUNK_FRACTION of chunks this big onto the maximum sized
+    * block.  We must further limit this value so that it's no more than
+    * MEMORYCHUNK_MAX_VALUE.  We're unable to have non-external chunks larger
+    * than that value as we store the chunk size in the MemoryChunk 'value'
+    * field in the call to MemoryChunkSetHdrMask().
+    */
+   set->allocChunkLimit = Min(maxBlockSize, MEMORYCHUNK_MAX_VALUE);
+   while ((Size) (set->allocChunkLimit + Bump_CHUNKHDRSZ) >
+          (Size) ((Size) (maxBlockSize - Bump_BLOCKHDRSZ) / Bump_CHUNK_FRACTION))
+       set->allocChunkLimit >>= 1;
+
+   /* Finally, do the type-independent part of context creation */
+   MemoryContextCreate((MemoryContext) set, T_BumpContext, MCTX_BUMP_ID,
+                       parent, name);
+
+   ((MemoryContext) set)->mem_allocated = allocSize;
+
+   return (MemoryContext) set;
+}
+
+/*
+ * BumpReset
+ *     Frees all memory which is allocated in the given set.
+ *
+ * The code simply frees all the blocks in the context apart from the keeper
+ * block.
+ */
+void
+BumpReset(MemoryContext context)
+{
+   BumpContext *set = (BumpContext *) context;
+   dlist_mutable_iter miter;
+
+   Assert(BumpIsValid(set));
+
+#ifdef MEMORY_CONTEXT_CHECKING
+   /* Check for corruption and leaks before freeing */
+   BumpCheck(context);
+#endif
+
+   dlist_foreach_modify(miter, &set->blocks)
+   {
+       BumpBlock  *block = dlist_container(BumpBlock, node, miter.cur);
+
+       if (IsKeeperBlock(set, block))
+           BumpBlockMarkEmpty(block);
+       else
+           BumpBlockFree(set, block);
+   }
+
+   /* Reset block size allocation sequence, too */
+   set->nextBlockSize = set->initBlockSize;
+
+   /* 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)));
+}
+
+/*
+ * BumpDelete
+ *     Free all memory which is allocated in the given context.
+ */
+void
+BumpDelete(MemoryContext context)
+{
+   /* Reset to release all releasable BumpBlocks */
+   BumpReset(context);
+   /* And free the context header and keeper block */
+   free(context);
+}
+
+/*
+ * Helper for BumpAlloc() that allocates an entire block for the chunk.
+ *
+ * BumpAlloc()'s comment explains why this is separate.
+ */
+pg_noinline
+static void *
+BumpAllocLarge(MemoryContext context, Size size, int flags)
+{
+   BumpContext *set = (BumpContext *) context;
+   BumpBlock  *block;
+#ifdef MEMORY_CONTEXT_CHECKING
+   MemoryChunk *chunk;
+#endif
+   Size        chunk_size;
+   Size        required_size;
+   Size        blksize;
+
+   /* validate 'size' is within the limits for the given 'flags' */
+   MemoryContextCheckSize(context, size, flags);
+
+#ifdef MEMORY_CONTEXT_CHECKING
+   /* ensure there's always space for the sentinel byte */
+   chunk_size = MAXALIGN(size + 1);
+#else
+   chunk_size = MAXALIGN(size);
+#endif
+
+   required_size = chunk_size + Bump_CHUNKHDRSZ;
+   blksize = required_size + Bump_BLOCKHDRSZ;
+
+   block = (BumpBlock *) malloc(blksize);
+   if (block == NULL)
+       return NULL;
+
+   context->mem_allocated += blksize;
+
+   /* the block is completely full */
+   block->freeptr = block->endptr = ((char *) block) + blksize;
+
+#ifdef MEMORY_CONTEXT_CHECKING
+   /* block with a single (used) chunk */
+   block->context = set;
+
+   chunk = (MemoryChunk *) (((char *) block) + Bump_BLOCKHDRSZ);
+
+   /* mark the MemoryChunk as externally managed */
+   MemoryChunkSetHdrMaskExternal(chunk, MCTX_BUMP_ID);
+
+   chunk->requested_size = size;
+   /* set mark to catch clobber of "unused" space */
+   Assert(size < chunk_size);
+   set_sentinel(MemoryChunkGetPointer(chunk), size);
+#endif
+#ifdef RANDOMIZE_ALLOCATED_MEMORY
+   /* fill the allocated space with junk */
+   randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
+#endif
+
+   /* add the block to the list of allocated blocks */
+   dlist_push_head(&set->blocks, &block->node);
+
+#ifdef MEMORY_CONTEXT_CHECKING
+   /* Ensure any padding bytes are marked NOACCESS. */
+   VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
+                              chunk_size - size);
+
+   /* Disallow access to the chunk header. */
+   VALGRIND_MAKE_MEM_NOACCESS(chunk, Bump_CHUNKHDRSZ);
+
+   return MemoryChunkGetPointer(chunk);
+#else
+   return (void *) (((char *) block) + Bump_BLOCKHDRSZ);
+#endif
+}
+
+/*
+ * Small helper for allocating a new chunk from a chunk, to avoid duplicating
+ * the code between BumpAlloc() and BumpAllocFromNewBlock().
+ */
+static inline void *
+BumpAllocChunkFromBlock(MemoryContext context, BumpBlock *block, Size size,
+                       Size chunk_size)
+{
+#ifdef MEMORY_CONTEXT_CHECKING
+   MemoryChunk *chunk;
+#else
+   void       *ptr;
+#endif
+
+   /* validate we've been given a block with enough free space */
+   Assert(block != NULL);
+   Assert((block->endptr - block->freeptr) >= Bump_CHUNKHDRSZ + chunk_size);
+
+#ifdef MEMORY_CONTEXT_CHECKING
+   chunk = (MemoryChunk *) block->freeptr;
+#else
+   ptr = (void *) block->freeptr;
+#endif
+
+   /* point the freeptr beyond this chunk */
+   block->freeptr += (Bump_CHUNKHDRSZ + chunk_size);
+   Assert(block->freeptr <= block->endptr);
+
+#ifdef MEMORY_CONTEXT_CHECKING
+   /* Prepare to initialize the chunk header. */
+   VALGRIND_MAKE_MEM_UNDEFINED(chunk, Bump_CHUNKHDRSZ);
+
+   MemoryChunkSetHdrMask(chunk, block, chunk_size, MCTX_BUMP_ID);
+   chunk->requested_size = size;
+   /* set mark to catch clobber of "unused" space */
+   Assert(size < chunk_size);
+   set_sentinel(MemoryChunkGetPointer(chunk), size);
+
+#ifdef RANDOMIZE_ALLOCATED_MEMORY
+   /* fill the allocated space with junk */
+   randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
+#endif
+
+   /* Ensure any padding bytes are marked NOACCESS. */
+   VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
+                              chunk_size - size);
+
+   /* Disallow access to the chunk header. */
+   VALGRIND_MAKE_MEM_NOACCESS(chunk, Bump_CHUNKHDRSZ);
+
+   return MemoryChunkGetPointer(chunk);
+#else
+   return ptr;
+#endif                         /* MEMORY_CONTEXT_CHECKING */
+}
+
+/*
+ * Helper for BumpAlloc() that allocates a new block and returns a chunk
+ * allocated from it.
+ *
+ * BumpAlloc()'s comment explains why this is separate.
+ */
+pg_noinline
+static void *
+BumpAllocFromNewBlock(MemoryContext context, Size size, int flags,
+                     Size chunk_size)
+{
+   BumpContext *set = (BumpContext *) context;
+   BumpBlock  *block;
+   Size        blksize;
+   Size        required_size;
+
+   /*
+    * 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;
+
+   /* we'll need space for the chunk, chunk hdr and block hdr */
+   required_size = chunk_size + Bump_CHUNKHDRSZ + Bump_BLOCKHDRSZ;
+   /* round the size up to the next power of 2 */
+   if (blksize < required_size)
+       blksize = pg_nextpower2_size_t(required_size);
+
+   block = (BumpBlock *) malloc(blksize);
+
+   if (block == NULL)
+       return MemoryContextAllocationFailure(context, size, flags);
+
+   context->mem_allocated += blksize;
+
+   /* initialize the new block */
+   BumpBlockInit(set, block, blksize);
+
+   /* add it to the doubly-linked list of blocks */
+   dlist_push_head(&set->blocks, &block->node);
+
+   return BumpAllocChunkFromBlock(context, block, size, chunk_size);
+}
+
+/*
+ * BumpAlloc
+ *     Returns a pointer to allocated memory of given size or raises an ERROR
+ *     on allocation failure, or returns NULL when flags contains
+ *     MCXT_ALLOC_NO_OOM.
+ *
+ * No request may exceed:
+ *     MAXALIGN_DOWN(SIZE_MAX) - Bump_BLOCKHDRSZ - Bump_CHUNKHDRSZ
+ * All callers use a much-lower limit.
+ *
+ *
+ * Note: when using valgrind, it doesn't matter how the returned allocation
+ * is marked, as mcxt.c will set it to UNDEFINED.
+ * This function should only contain the most common code paths.  Everything
+ * else should be in pg_noinline helper functions, thus avoiding the overhead
+ * of creating a stack frame for the common cases.  Allocating memory is often
+ * a bottleneck in many workloads, so avoiding stack frame setup is
+ * worthwhile.  Helper functions should always directly return the newly
+ * allocated memory so that we can just return that address directly as a tail
+ * call.
+ */
+void *
+BumpAlloc(MemoryContext context, Size size, int flags)
+{
+   BumpContext *set = (BumpContext *) context;
+   BumpBlock  *block;
+   Size        chunk_size;
+   Size        required_size;
+
+   Assert(BumpIsValid(set));
+
+#ifdef MEMORY_CONTEXT_CHECKING
+   /* ensure there's always space for the sentinel byte */
+   chunk_size = MAXALIGN(size + 1);
+#else
+   chunk_size = MAXALIGN(size);
+#endif
+
+   /*
+    * If requested size exceeds maximum for chunks we hand the the request
+    * off to BumpAllocLarge().
+    */
+   if (chunk_size > set->allocChunkLimit)
+       return BumpAllocLarge(context, size, flags);
+
+   required_size = chunk_size + Bump_CHUNKHDRSZ;
+
+   /*
+    * Not an oversized chunk.  We try to first make use of the latest block,
+    * but if there's not enough space in it we must allocate a new block.
+    */
+   block = dlist_container(BumpBlock, node, dlist_head_node(&set->blocks));
+
+   if (BumpBlockFreeBytes(block) < required_size)
+       return BumpAllocFromNewBlock(context, size, flags, chunk_size);
+
+   /* The current block has space, so just allocate chunk there. */
+   return BumpAllocChunkFromBlock(context, block, size, chunk_size);
+}
+
+/*
+ * BumpBlockInit
+ *     Initializes 'block' assuming 'blksize'.  Does not update the context's
+ *     mem_allocated field.
+ */
+static inline void
+BumpBlockInit(BumpContext *context, BumpBlock *block, Size blksize)
+{
+#ifdef MEMORY_CONTEXT_CHECKING
+   block->context = context;
+#endif
+   block->freeptr = ((char *) block) + Bump_BLOCKHDRSZ;
+   block->endptr = ((char *) block) + blksize;
+
+   /* Mark unallocated space NOACCESS. */
+   VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, blksize - Bump_BLOCKHDRSZ);
+}
+
+/*
+ * BumpBlockIsEmpty
+ *     Returns true iff 'block' contains no chunks
+ */
+static inline bool
+BumpBlockIsEmpty(BumpBlock *block)
+{
+   /* it's empty if the freeptr has not moved */
+   return (block->freeptr == ((char *) block + Bump_BLOCKHDRSZ));
+}
+
+/*
+ * BumpBlockMarkEmpty
+ *     Set a block as empty.  Does not free the block.
+ */
+static inline void
+BumpBlockMarkEmpty(BumpBlock *block)
+{
+#if defined(USE_VALGRIND) || defined(CLOBBER_FREED_MEMORY)
+   char       *datastart = ((char *) block) + Bump_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->freeptr = ((char *) block) + Bump_BLOCKHDRSZ;
+}
+
+/*
+ * BumpBlockFreeBytes
+ *     Returns the number of bytes free in 'block'
+ */
+static inline Size
+BumpBlockFreeBytes(BumpBlock *block)
+{
+   return (block->endptr - block->freeptr);
+}
+
+/*
+ * BumpBlockFree
+ *     Remove 'block' from 'set' and release the memory consumed by it.
+ */
+static inline void
+BumpBlockFree(BumpContext *set, BumpBlock *block)
+{
+   /* Make sure nobody tries to free the keeper block */
+   Assert(!IsKeeperBlock(set, block));
+
+   /* release the block from the list of blocks */
+   dlist_delete(&block->node);
+
+   ((MemoryContext) set)->mem_allocated -= ((char *) block->endptr - (char *) block);
+
+#ifdef CLOBBER_FREED_MEMORY
+   wipe_mem(block, ((char *) block->endptr - (char *) block));
+#endif
+
+   free(block);
+}
+
+/*
+ * BumpFree
+ *     Unsupported.
+ */
+void
+BumpFree(void *pointer)
+{
+   elog(ERROR, "pfree is not supported by the bump memory allocator");
+}
+
+/*
+ * BumpRealloc
+ *     Unsupported.
+ */
+void *
+BumpRealloc(void *pointer, Size size, int flags)
+{
+   elog(ERROR, "%s is not supported by the bump memory allocator", "realloc");
+   return NULL;                /* keep compiler quiet */
+}
+
+/*
+ * BumpGetChunkContext
+ *     Unsupported.
+ */
+MemoryContext
+BumpGetChunkContext(void *pointer)
+{
+   elog(ERROR, "%s is not supported by the bump memory allocator", "GetMemoryChunkContext");
+   return NULL;                /* keep compiler quiet */
+}
+
+/*
+* BumpGetChunkSpace
+*      Given a currently-allocated chunk, determine the total space
+*      it occupies (including all memory-allocation overhead).
+*/
+Size
+BumpGetChunkSpace(void *pointer)
+{
+   elog(ERROR, "%s is not supported by the bump memory allocator", "GetMemoryChunkSpace");
+   return 0;                   /* keep compiler quiet */
+}
+
+/*
+ * BumpIsEmpty
+ *     Is a BumpContext empty of any allocated space?
+ */
+bool
+BumpIsEmpty(MemoryContext context)
+{
+   BumpContext *set = (BumpContext *) context;
+   dlist_iter  iter;
+
+   Assert(BumpIsValid(set));
+
+   dlist_foreach(iter, &set->blocks)
+   {
+       BumpBlock  *block = dlist_container(BumpBlock, node, iter.cur);
+
+       if (!BumpBlockIsEmpty(block))
+           return false;
+   }
+
+   return true;
+}
+
+/*
+ * BumpStats
+ *     Compute stats about memory consumption of a Bump context.
+ *
+ * printfunc: if not NULL, pass a human-readable stats string to this.
+ * passthru: pass this pointer through to printfunc.
+ * totals: if not NULL, add stats about this context into *totals.
+ * print_to_stderr: print stats to stderr if true, elog otherwise.
+ */
+void
+BumpStats(MemoryContext context, MemoryStatsPrintFunc printfunc,
+         void *passthru, MemoryContextCounters *totals, bool print_to_stderr)
+{
+   BumpContext *set = (BumpContext *) context;
+   Size        nblocks = 0;
+   Size        totalspace = 0;
+   Size        freespace = 0;
+   dlist_iter  iter;
+
+   Assert(BumpIsValid(set));
+
+   dlist_foreach(iter, &set->blocks)
+   {
+       BumpBlock  *block = dlist_container(BumpBlock, node, iter.cur);
+
+       nblocks++;
+       totalspace += (block->endptr - (char *) block);
+       freespace += (block->endptr - block->freeptr);
+   }
+
+   if (printfunc)
+   {
+       char        stats_string[200];
+
+       snprintf(stats_string, sizeof(stats_string),
+                "%zu total in %zu blocks; %zu free; %zu used",
+                totalspace, nblocks, freespace, totalspace - freespace);
+       printfunc(context, passthru, stats_string, print_to_stderr);
+   }
+
+   if (totals)
+   {
+       totals->nblocks += nblocks;
+       totals->totalspace += totalspace;
+       totals->freespace += freespace;
+   }
+}
+
+
+#ifdef MEMORY_CONTEXT_CHECKING
+
+/*
+ * BumpCheck
+ *     Walk through chunks and check consistency of memory.
+ *
+ * NOTE: report errors as WARNING, *not* ERROR or FATAL.  Otherwise you'll
+ * find yourself in an infinite loop when trouble occurs, because this
+ * routine will be entered again when elog cleanup tries to release memory!
+ */
+void
+BumpCheck(MemoryContext context)
+{
+   BumpContext *bump = (BumpContext *) context;
+   const char *name = context->name;
+   dlist_iter  iter;
+   Size        total_allocated = 0;
+
+   /* walk all blocks in this context */
+   dlist_foreach(iter, &bump->blocks)
+   {
+       BumpBlock  *block = dlist_container(BumpBlock, node, iter.cur);
+       int         nchunks;
+       char       *ptr;
+       bool        has_external_chunk = false;
+
+       if (IsKeeperBlock(bump, block))
+           total_allocated += block->endptr - (char *) bump;
+       else
+           total_allocated += block->endptr - (char *) block;
+
+       /* check block belongs to the correct context */
+       if (block->context != bump)
+           elog(WARNING, "problem in Bump %s: bogus context link in block %p",
+                name, block);
+
+       /* now walk through the chunks and count them */
+       nchunks = 0;
+       ptr = ((char *) block) + Bump_BLOCKHDRSZ;
+
+       while (ptr < block->freeptr)
+       {
+           MemoryChunk *chunk = (MemoryChunk *) ptr;
+           BumpBlock  *chunkblock;
+           Size        chunksize;
+
+           /* allow access to the chunk header */
+           VALGRIND_MAKE_MEM_DEFINED(chunk, Bump_CHUNKHDRSZ);
+
+           if (MemoryChunkIsExternal(chunk))
+           {
+               chunkblock = ExternalChunkGetBlock(chunk);
+               chunksize = block->endptr - (char *) MemoryChunkGetPointer(chunk);
+               has_external_chunk = true;
+           }
+           else
+           {
+               chunkblock = MemoryChunkGetBlock(chunk);
+               chunksize = MemoryChunkGetValue(chunk);
+           }
+
+           /* move to the next chunk */
+           ptr += (chunksize + Bump_CHUNKHDRSZ);
+
+           nchunks += 1;
+
+           /* chunks have both block and context pointers, so check both */
+           if (chunkblock != block)
+               elog(WARNING, "problem in Bump %s: bogus block link in block %p, chunk %p",
+                    name, block, chunk);
+       }
+
+       if (has_external_chunk && nchunks > 1)
+           elog(WARNING, "problem in Bump %s: external chunk on non-dedicated block %p",
+                name, block);
+
+   }
+
+   Assert(total_allocated == context->mem_allocated);
+}
+
+#endif                         /* MEMORY_CONTEXT_CHECKING */
index b2143b2fc6ca55a4f85f9d9c9c3ac61d3796e6d4..b42ececbe78f41f946af5389492292ce10e63c4f 100644 (file)
@@ -100,6 +100,20 @@ static const MemoryContextMethods mcxt_methods[] = {
    [MCTX_ALIGNED_REDIRECT_ID].check = NULL,    /* not required */
 #endif
 
+   /* bump.c */
+   [MCTX_BUMP_ID].alloc = BumpAlloc,
+   [MCTX_BUMP_ID].free_p = BumpFree,
+   [MCTX_BUMP_ID].realloc = BumpRealloc,
+   [MCTX_BUMP_ID].reset = BumpReset,
+   [MCTX_BUMP_ID].delete_context = BumpDelete,
+   [MCTX_BUMP_ID].get_chunk_context = BumpGetChunkContext,
+   [MCTX_BUMP_ID].get_chunk_space = BumpGetChunkSpace,
+   [MCTX_BUMP_ID].is_empty = BumpIsEmpty,
+   [MCTX_BUMP_ID].stats = BumpStats,
+#ifdef MEMORY_CONTEXT_CHECKING
+   [MCTX_BUMP_ID].check = BumpCheck,
+#endif
+
 
    /*
     * Reserved and unused IDs should have dummy entries here.  This allows us
@@ -109,7 +123,6 @@ static const MemoryContextMethods mcxt_methods[] = {
     */
    BOGUS_MCTX(MCTX_1_RESERVED_GLIBC_ID),
    BOGUS_MCTX(MCTX_2_RESERVED_GLIBC_ID),
-   BOGUS_MCTX(MCTX_7_UNUSED_ID),
    BOGUS_MCTX(MCTX_8_UNUSED_ID),
    BOGUS_MCTX(MCTX_9_UNUSED_ID),
    BOGUS_MCTX(MCTX_10_UNUSED_ID),
index 9dcf990cdcc5e8edd0d38ab408656656ee67870d..dd43a6844c7c909de26d7314e80b644b346b8a86 100644 (file)
@@ -3,6 +3,7 @@
 backend_sources += files(
   'alignedalloc.c',
   'aset.c',
+  'bump.c',
   'dsa.c',
   'freepage.c',
   'generation.c',
index edc0257f36d4dae7da545f74a48b27eb46c25618..c4c9fd3e3e1bb279e515c7d741a7ea43aad25aec 100644 (file)
@@ -146,6 +146,7 @@ typedef struct MemoryContextData
    ((context) != NULL && \
     (IsA((context), AllocSetContext) || \
      IsA((context), SlabContext) || \
-     IsA((context), GenerationContext)))
+     IsA((context), GenerationContext) || \
+     IsA((context), BumpContext)))
 
 #endif                         /* MEMNODES_H */
index 6e5fa72b0e179103e5ef77b3181c73054cdcefcb..cd9596ff21930dce7c4eab7ede5ff77f0f78472e 100644 (file)
@@ -143,6 +143,13 @@ extern MemoryContext GenerationContextCreate(MemoryContext parent,
                                             Size initBlockSize,
                                             Size maxBlockSize);
 
+/* bump.c */
+extern MemoryContext BumpContextCreate(MemoryContext parent,
+                                      const char *name,
+                                      Size minContextSize,
+                                      Size initBlockSize,
+                                      Size maxBlockSize);
+
 /*
  * Recommended default alloc parameters, suitable for "ordinary" contexts
  * that might hold quite a lot of data.
index 2d03261155640217cacfcee9a5dac13b4f2e53d8..5ce2d9b5877a521e25f8386b7932e51e7cefc024 100644 (file)
@@ -79,6 +79,22 @@ extern void *AlignedAllocRealloc(void *pointer, Size size, int flags);
 extern MemoryContext AlignedAllocGetChunkContext(void *pointer);
 extern Size AlignedAllocGetChunkSpace(void *pointer);
 
+ /* These functions implement the MemoryContext API for the Bump context. */
+extern void *BumpAlloc(MemoryContext context, Size size, int flags);
+extern void BumpFree(void *pointer);
+extern void *BumpRealloc(void *pointer, Size size, int flags);
+extern void BumpReset(MemoryContext context);
+extern void BumpDelete(MemoryContext context);
+extern MemoryContext BumpGetChunkContext(void *pointer);
+extern Size BumpGetChunkSpace(void *pointer);
+extern bool BumpIsEmpty(MemoryContext context);
+extern void BumpStats(MemoryContext context, MemoryStatsPrintFunc printfunc,
+                     void *passthru, MemoryContextCounters *totals,
+                     bool print_to_stderr);
+#ifdef MEMORY_CONTEXT_CHECKING
+extern void BumpCheck(MemoryContext context);
+#endif
+
 /*
  * How many extra bytes do we need to request in order to ensure that we can
  * align a pointer to 'alignto'.  Since palloc'd pointers are already aligned
@@ -111,7 +127,7 @@ typedef enum MemoryContextMethodID
    MCTX_GENERATION_ID,
    MCTX_SLAB_ID,
    MCTX_ALIGNED_REDIRECT_ID,
-   MCTX_7_UNUSED_ID,
+   MCTX_BUMP_ID,
    MCTX_8_UNUSED_ID,
    MCTX_9_UNUSED_ID,
    MCTX_10_UNUSED_ID,
index e608fd39d965824e8ddac50ef9b9ede7e3edc23b..6e0717c8c473995b9d286527301ed2ac6b636cfe 100644 (file)
@@ -337,6 +337,8 @@ BulkInsertState
 BulkInsertStateData
 BulkWriteBuffer
 BulkWriteState
+BumpBlock
+BumpContext
 CACHESIGN
 CAC_state
 CCFastEqualFN