]> granicus.if.org Git - postgresql/commitdiff
Rethink MemoryContext creation to improve performance.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 13 Dec 2017 18:55:12 +0000 (13:55 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 13 Dec 2017 18:55:16 +0000 (13:55 -0500)
This patch makes a number of interrelated changes to reduce the overhead
involved in creating/deleting memory contexts.  The key ideas are:

* Include the AllocSetContext header of an aset.c context in its first
malloc request, rather than allocating it separately in TopMemoryContext.
This means that we now always create an initial or "keeper" block in an
aset, even if it never receives any allocation requests.

* Create freelists in which we can save and recycle recently-destroyed
asets (this idea is due to Robert Haas).

* In the common case where the name of a context is a constant string,
just store a pointer to it in the context header, rather than copying
the string.

The first change eliminates a palloc/pfree cycle per context, and
also avoids bloat in TopMemoryContext, at the price that creating
a context now involves a malloc/free cycle even if the context never
receives any allocations.  That would be a loser for some common
usage patterns, but recycling short-lived contexts via the freelist
eliminates that pain.

Avoiding copying constant strings not only saves strlen() and strcpy()
overhead, but is an essential part of the freelist optimization because
it makes the context header size constant.  Currently we make no
attempt to use the freelist for contexts with non-constant names.
(Perhaps someday we'll need to think harder about that, but in current
usage, most contexts with custom names are long-lived anyway.)

The freelist management in this initial commit is pretty simplistic,
and we might want to refine it later --- but in common workloads that
will never matter because the freelists will never get full anyway.

To create a context with a non-constant name, one is now required to
call AllocSetContextCreateExtended and specify the MEMCONTEXT_COPY_NAME
option.  AllocSetContextCreate becomes a wrapper macro, and it includes
a test that will complain about non-string-literal context name
parameters on gcc and similar compilers.

An unfortunate side effect of making AllocSetContextCreate a macro is
that one is now *required* to use the size parameter abstraction macros
(ALLOCSET_DEFAULT_SIZES and friends) with it; the pre-9.6 habit of
writing out individual size parameters no longer works unless you
switch to AllocSetContextCreateExtended.

Internally to the memory-context-related modules, the context creation
APIs are simplified, removing the rather baroque original design whereby
a context-type module called mcxt.c which then called back into the
context-type module.  That saved a bit of code duplication, but not much,
and it prevented context-type modules from exercising control over the
allocation of context headers.

In passing, I converted the test-and-elog validation of aset size
parameters into Asserts to save a few more cycles.  The original thought
was that callers might compute size parameters on the fly, but in practice
nobody does that, so it's useless to expend cycles on checking those
numbers in production builds.

Also, mark the memory context method-pointer structs "const",
just for cleanliness.

Discussion: https://postgr.es/m/2264.1512870796@sss.pgh.pa.us

21 files changed:
contrib/amcheck/verify_nbtree.c
src/backend/access/transam/xact.c
src/backend/catalog/partition.c
src/backend/commands/subscriptioncmds.c
src/backend/lib/knapsack.c
src/backend/replication/logical/launcher.c
src/backend/replication/logical/reorderbuffer.c
src/backend/replication/pgoutput/pgoutput.c
src/backend/utils/cache/relcache.c
src/backend/utils/cache/ts_cache.c
src/backend/utils/hash/dynahash.c
src/backend/utils/mmgr/README
src/backend/utils/mmgr/aset.c
src/backend/utils/mmgr/generation.c
src/backend/utils/mmgr/mcxt.c
src/backend/utils/mmgr/slab.c
src/include/nodes/memnodes.h
src/include/utils/memutils.h
src/pl/plperl/plperl.c
src/pl/plpython/plpy_procedure.c
src/pl/tcl/pltcl.c

index 868c14ec8feec7a3293481bc3f03d9ef07d1f2fa..adbbc444681a3daef05a92a44f45f63bd0b41553 100644 (file)
@@ -295,9 +295,7 @@ bt_check_every_level(Relation rel, bool readonly)
        /* Create context for page */
        state->targetcontext = AllocSetContextCreate(CurrentMemoryContext,
                                                                                                 "amcheck context",
-                                                                                                ALLOCSET_DEFAULT_MINSIZE,
-                                                                                                ALLOCSET_DEFAULT_INITSIZE,
-                                                                                                ALLOCSET_DEFAULT_MAXSIZE);
+                                                                                                ALLOCSET_DEFAULT_SIZES);
        state->checkstrategy = GetAccessStrategy(BAS_BULKREAD);
 
        /* Get true root block from meta-page */
index 046898c6190edecbf30e6d04970f6c6d0e9d05cc..e93d740b210d636fc85b1c03f5ac29a740f1d408 100644 (file)
@@ -997,11 +997,12 @@ AtStart_Memory(void)
         */
        if (TransactionAbortContext == NULL)
                TransactionAbortContext =
-                       AllocSetContextCreate(TopMemoryContext,
-                                                                 "TransactionAbortContext",
-                                                                 32 * 1024,
-                                                                 32 * 1024,
-                                                                 32 * 1024);
+                       AllocSetContextCreateExtended(TopMemoryContext,
+                                                                                 "TransactionAbortContext",
+                                                                                 0,
+                                                                                 32 * 1024,
+                                                                                 32 * 1024,
+                                                                                 32 * 1024);
 
        /*
         * We shouldn't have a transaction context already.
index ef156e449e5eb75afd1335a00fe68b2232e76240..5c4018e9f7a089268cbda1c9720a2396a30aee9c 100644 (file)
@@ -520,9 +520,10 @@ RelationBuildPartitionDesc(Relation rel)
        }
 
        /* Now build the actual relcache partition descriptor */
-       rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
-                                                                                 RelationGetRelationName(rel),
-                                                                                 ALLOCSET_DEFAULT_SIZES);
+       rel->rd_pdcxt = AllocSetContextCreateExtended(CacheMemoryContext,
+                                                                                                 RelationGetRelationName(rel),
+                                                                                                 MEMCONTEXT_COPY_NAME,
+                                                                                                 ALLOCSET_DEFAULT_SIZES);
        oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
 
        result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
index 086a6ef85ea8b0d87d7bb18e1b8a98bd77c41f74..a7f426d52b95e30886bc46d9b2c7c37fe2a71d12 100644 (file)
@@ -259,9 +259,7 @@ publicationListToArray(List *publist)
        /* Create memory context for temporary allocations. */
        memcxt = AllocSetContextCreate(CurrentMemoryContext,
                                                                   "publicationListToArray to array",
-                                                                  ALLOCSET_DEFAULT_MINSIZE,
-                                                                  ALLOCSET_DEFAULT_INITSIZE,
-                                                                  ALLOCSET_DEFAULT_MAXSIZE);
+                                                                  ALLOCSET_DEFAULT_SIZES);
        oldcxt = MemoryContextSwitchTo(memcxt);
 
        datums = (Datum *) palloc(sizeof(Datum) * list_length(publist));
index ddf2b9afa33699785b51f2cfbc4383b3a58a164e..490c0cc73c37fafbe82ce4d8d9d0b78d9acdbf5d 100644 (file)
@@ -57,9 +57,7 @@ DiscreteKnapsack(int max_weight, int num_items,
 {
        MemoryContext local_ctx = AllocSetContextCreate(CurrentMemoryContext,
                                                                                                        "Knapsack",
-                                                                                                       ALLOCSET_SMALL_MINSIZE,
-                                                                                                       ALLOCSET_SMALL_INITSIZE,
-                                                                                                       ALLOCSET_SMALL_MAXSIZE);
+                                                                                                       ALLOCSET_SMALL_SIZES);
        MemoryContext oldctx = MemoryContextSwitchTo(local_ctx);
        double     *values;
        Bitmapset **sets;
index a613ef4757bec209460cf0a1c038190e4cea17ac..24be3cef538640a60101d23fb0290a3eed010e6e 100644 (file)
@@ -925,9 +925,7 @@ ApplyLauncherMain(Datum main_arg)
                        /* Use temporary context for the database list and worker info. */
                        subctx = AllocSetContextCreate(TopMemoryContext,
                                                                                   "Logical Replication Launcher sublist",
-                                                                                  ALLOCSET_DEFAULT_MINSIZE,
-                                                                                  ALLOCSET_DEFAULT_INITSIZE,
-                                                                                  ALLOCSET_DEFAULT_MAXSIZE);
+                                                                                  ALLOCSET_DEFAULT_SIZES);
                        oldctx = MemoryContextSwitchTo(subctx);
 
                        /* search for subscriptions to start or stop. */
index fa95bab58eabec5ec0bedae1fb8e1d9cc5c303eb..5ac391dbda3c174a76fe01106f20f1d657a1463b 100644 (file)
@@ -237,16 +237,19 @@ ReorderBufferAllocate(void)
 
        buffer->change_context = SlabContextCreate(new_ctx,
                                                                                           "Change",
+                                                                                          0,
                                                                                           SLAB_DEFAULT_BLOCK_SIZE,
                                                                                           sizeof(ReorderBufferChange));
 
        buffer->txn_context = SlabContextCreate(new_ctx,
                                                                                        "TXN",
+                                                                                       0,
                                                                                        SLAB_DEFAULT_BLOCK_SIZE,
                                                                                        sizeof(ReorderBufferTXN));
 
        buffer->tup_context = GenerationContextCreate(new_ctx,
                                                                                                  "Tuples",
+                                                                                                 0,
                                                                                                  SLAB_LARGE_BLOCK_SIZE);
 
        hash_ctl.keysize = sizeof(TransactionId);
index c3126545b487dcbe16dcd1321d892b1b31b0443b..550b156e2d471d852e2fba969c790a7318249f33 100644 (file)
@@ -152,9 +152,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
        /* Create our memory context for private allocations. */
        data->context = AllocSetContextCreate(ctx->context,
                                                                                  "logical replication output context",
-                                                                                 ALLOCSET_DEFAULT_MINSIZE,
-                                                                                 ALLOCSET_DEFAULT_INITSIZE,
-                                                                                 ALLOCSET_DEFAULT_MAXSIZE);
+                                                                                 ALLOCSET_DEFAULT_SIZES);
 
        ctx->output_plugin_private = data;
 
index 12a5f157c0a47c7da86577da8b2e242f38233be8..3a9233ef3dda44963ad29e7b14397ce1ace21bec 100644 (file)
@@ -669,9 +669,10 @@ RelationBuildRuleLock(Relation relation)
        /*
         * Make the private context.  Assume it'll not contain much data.
         */
-       rulescxt = AllocSetContextCreate(CacheMemoryContext,
-                                                                        RelationGetRelationName(relation),
-                                                                        ALLOCSET_SMALL_SIZES);
+       rulescxt = AllocSetContextCreateExtended(CacheMemoryContext,
+                                                                                        RelationGetRelationName(relation),
+                                                                                        MEMCONTEXT_COPY_NAME,
+                                                                                        ALLOCSET_SMALL_SIZES);
        relation->rd_rulescxt = rulescxt;
 
        /*
@@ -984,9 +985,10 @@ RelationBuildPartitionKey(Relation relation)
        ReleaseSysCache(tuple);
 
        /* Success --- now copy to the cache memory */
-       partkeycxt = AllocSetContextCreate(CacheMemoryContext,
-                                                                          RelationGetRelationName(relation),
-                                                                          ALLOCSET_SMALL_SIZES);
+       partkeycxt = AllocSetContextCreateExtended(CacheMemoryContext,
+                                                                                          RelationGetRelationName(relation),
+                                                                                          MEMCONTEXT_COPY_NAME,
+                                                                                          ALLOCSET_SMALL_SIZES);
        relation->rd_partkeycxt = partkeycxt;
        oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
        relation->rd_partkey = copy_partition_key(key);
@@ -1566,9 +1568,10 @@ RelationInitIndexAccessInfo(Relation relation)
         * a context, and not just a couple of pallocs, is so that we won't leak
         * any subsidiary info attached to fmgr lookup records.
         */
-       indexcxt = AllocSetContextCreate(CacheMemoryContext,
-                                                                        RelationGetRelationName(relation),
-                                                                        ALLOCSET_SMALL_SIZES);
+       indexcxt = AllocSetContextCreateExtended(CacheMemoryContext,
+                                                                                        RelationGetRelationName(relation),
+                                                                                        MEMCONTEXT_COPY_NAME,
+                                                                                        ALLOCSET_SMALL_SIZES);
        relation->rd_indexcxt = indexcxt;
 
        /*
@@ -5537,9 +5540,11 @@ load_relcache_init_file(bool shared)
                         * prepare index info context --- parameters should match
                         * RelationInitIndexAccessInfo
                         */
-                       indexcxt = AllocSetContextCreate(CacheMemoryContext,
-                                                                                        RelationGetRelationName(rel),
-                                                                                        ALLOCSET_SMALL_SIZES);
+                       indexcxt =
+                               AllocSetContextCreateExtended(CacheMemoryContext,
+                                                                                         RelationGetRelationName(rel),
+                                                                                         MEMCONTEXT_COPY_NAME,
+                                                                                         ALLOCSET_SMALL_SIZES);
                        rel->rd_indexcxt = indexcxt;
 
                        /*
index da5c8ea345b01cb338106c972e2338d702dee990..29cf93a4de6c7c89b794ee888de8693ec64d070e 100644 (file)
@@ -294,9 +294,10 @@ lookup_ts_dictionary_cache(Oid dictId)
                        Assert(!found);         /* it wasn't there a moment ago */
 
                        /* Create private memory context the first time through */
-                       saveCtx = AllocSetContextCreate(CacheMemoryContext,
-                                                                                       NameStr(dict->dictname),
-                                                                                       ALLOCSET_SMALL_SIZES);
+                       saveCtx = AllocSetContextCreateExtended(CacheMemoryContext,
+                                                                                                       NameStr(dict->dictname),
+                                                                                                       MEMCONTEXT_COPY_NAME,
+                                                                                                       ALLOCSET_SMALL_SIZES);
                }
                else
                {
index 71f5f0688a079e42d332959c82553e8b9c7e3551..c88efc30544e2f708fc6c3eba16a63ef8f14afb3 100644 (file)
@@ -340,9 +340,11 @@ hash_create(const char *tabname, long nelem, HASHCTL *info, int flags)
                        CurrentDynaHashCxt = info->hcxt;
                else
                        CurrentDynaHashCxt = TopMemoryContext;
-               CurrentDynaHashCxt = AllocSetContextCreate(CurrentDynaHashCxt,
-                                                                                                  tabname,
-                                                                                                  ALLOCSET_DEFAULT_SIZES);
+               CurrentDynaHashCxt =
+                       AllocSetContextCreateExtended(CurrentDynaHashCxt,
+                                                                                 tabname,
+                                                                                 MEMCONTEXT_COPY_NAME,
+                                                                                 ALLOCSET_DEFAULT_SIZES);
        }
 
        /* Initialize the hash header, plus a copy of the table name */
index 296fa198dc9a8bbb48ab9f157cc120760784dc76..a42e568d5c98f54ae40b38073ad09fd173a14ee9 100644 (file)
@@ -177,8 +177,7 @@ every other context is a direct or indirect child of this one.  Allocating
 here is essentially the same as "malloc", because this context will never
 be reset or deleted.  This is for stuff that should live forever, or for
 stuff that the controlling module will take care of deleting at the
-appropriate time.  An example is fd.c's tables of open files, as well as
-the context management nodes for memory contexts themselves.  Avoid
+appropriate time.  An example is fd.c's tables of open files.  Avoid
 allocating stuff here unless really necessary, and especially avoid
 running with CurrentMemoryContext pointing here.
 
@@ -420,11 +419,10 @@ a maximum block size.  Selecting smaller values can prevent wastage of
 space in contexts that aren't expected to hold very much (an example
 is the relcache's per-relation contexts).
 
-Also, it is possible to specify a minimum context size.  If this
-value is greater than zero then a block of that size will be grabbed
-immediately upon context creation, and cleared but not released during
-context resets.  This feature is needed for ErrorContext (see above),
-but will most likely not be used for other contexts.
+Also, it is possible to specify a minimum context size, in case for some
+reason that should be different from the initial size for additional
+blocks.  An aset.c context will always contain at least one block,
+of size minContextSize if that is specified, otherwise initBlockSize.
 
 We expect that per-tuple contexts will be reset frequently and typically
 will not allocate very much space per tuple cycle.  To make this usage
index 1bd1c34fdef0a178fed33d2f48c03a393be9a7e8..1519da05d21dbe1195cd9f5a32dae13a83364c85 100644 (file)
@@ -93,6 +93,9 @@
  *
  * Blocks allocated to hold oversize chunks do not follow this rule, however;
  * they are just however big they need to be to hold that single chunk.
+ *
+ * Also, if a minContextSize is specified, the first block has that size,
+ * and then initBlockSize is used for the next one.
  *--------------------
  */
 
@@ -113,7 +116,7 @@ typedef void *AllocPointer;
  *
  * Note: header.isReset means there is nothing for AllocSetReset to do.
  * This is different from the aset being physically empty (empty blocks list)
- * because we may still have a keeper block.  It's also different from the set
+ * because we will still have a keeper block.  It's also different from the set
  * being logically empty, because we don't attempt to detect pfree'ing the
  * last active chunk.
  */
@@ -127,8 +130,11 @@ typedef struct AllocSetContext
        Size            initBlockSize;  /* initial block size */
        Size            maxBlockSize;   /* maximum block size */
        Size            nextBlockSize;  /* next block size to allocate */
+       Size            headerSize;             /* allocated size of context header */
        Size            allocChunkLimit;        /* effective chunk size limit */
-       AllocBlock      keeper;                 /* if not NULL, keep this block over resets */
+       AllocBlock      keeper;                 /* keep this block over resets */
+       /* freelist this context could be put in, or -1 if not a candidate: */
+       int                     freeListIndex;  /* index in context_freelists[], or -1 */
 } AllocSetContext;
 
 typedef AllocSetContext *AllocSet;
@@ -215,13 +221,57 @@ typedef struct AllocChunkData
 #define AllocChunkGetPointer(chk)      \
                                        ((AllocPointer)(((char *)(chk)) + ALLOC_CHUNKHDRSZ))
 
+/*
+ * Rather than repeatedly creating and deleting memory contexts, we keep some
+ * freed contexts in freelists so that we can hand them out again with little
+ * work.  Before putting a context in a freelist, we reset it so that it has
+ * only its initial malloc chunk and no others.  To be a candidate for a
+ * freelist, a context must have the same minContextSize/initBlockSize as
+ * other contexts in the list; but its maxBlockSize is irrelevant since that
+ * doesn't affect the size of the initial chunk.  Also, candidate contexts
+ * *must not* use MEMCONTEXT_COPY_NAME since that would make their header size
+ * variable.  (We currently insist that all flags be zero, since other flags
+ * would likely make the contexts less interchangeable, too.)
+ *
+ * We currently provide one freelist for ALLOCSET_DEFAULT_SIZES contexts
+ * and one for ALLOCSET_SMALL_SIZES contexts; the latter works for
+ * ALLOCSET_START_SMALL_SIZES too, since only the maxBlockSize differs.
+ *
+ * Ordinarily, we re-use freelist contexts in last-in-first-out order, in
+ * hopes of improving locality of reference.  But if there get to be too
+ * many contexts in the list, we'd prefer to drop the most-recently-created
+ * contexts in hopes of keeping the process memory map compact.
+ * We approximate that by simply deleting all existing entries when the list
+ * overflows, on the assumption that queries that allocate a lot of contexts
+ * will probably free them in more or less reverse order of allocation.
+ *
+ * Contexts in a freelist are chained via their nextchild pointers.
+ */
+#define MAX_FREE_CONTEXTS 100  /* arbitrary limit on freelist length */
+
+typedef struct AllocSetFreeList
+{
+       int                     num_free;               /* current list length */
+       AllocSetContext *first_free;    /* list header */
+} AllocSetFreeList;
+
+/* context_freelists[0] is for default params, [1] for small params */
+static AllocSetFreeList context_freelists[2] =
+{
+       {
+               0, NULL
+       },
+       {
+               0, NULL
+       }
+};
+
 /*
  * These functions implement the MemoryContext API for AllocSet contexts.
  */
 static void *AllocSetAlloc(MemoryContext context, Size size);
 static void AllocSetFree(MemoryContext context, void *pointer);
 static void *AllocSetRealloc(MemoryContext context, void *pointer, Size size);
-static void AllocSetInit(MemoryContext context);
 static void AllocSetReset(MemoryContext context);
 static void AllocSetDelete(MemoryContext context);
 static Size AllocSetGetChunkSpace(MemoryContext context, void *pointer);
@@ -236,11 +286,10 @@ static void AllocSetCheck(MemoryContext context);
 /*
  * This is the virtual function table for AllocSet contexts.
  */
-static MemoryContextMethods AllocSetMethods = {
+static const MemoryContextMethods AllocSetMethods = {
        AllocSetAlloc,
        AllocSetFree,
        AllocSetRealloc,
-       AllocSetInit,
        AllocSetReset,
        AllocSetDelete,
        AllocSetGetChunkSpace,
@@ -325,27 +374,35 @@ AllocSetFreeIndex(Size size)
 
 
 /*
- * AllocSetContextCreate
+ * AllocSetContextCreateExtended
  *             Create a new AllocSet context.
  *
  * parent: parent context, or NULL if top-level context
  * name: name of context (for debugging only, need not be unique)
+ * flags: bitmask of MEMCONTEXT_XXX option flags
  * minContextSize: minimum context size
  * initBlockSize: initial allocation block size
  * maxBlockSize: maximum allocation block size
  *
- * Notes: the name string will be copied into context-lifespan storage.
+ * Notes: if flags & MEMCONTEXT_COPY_NAME, the name string will be copied into
+ * context-lifespan storage; otherwise, it had better be statically allocated.
  * Most callers should abstract the context size parameters using a macro
- * such as ALLOCSET_DEFAULT_SIZES.
+ * such as ALLOCSET_DEFAULT_SIZES.  (This is now *required* when going
+ * through the AllocSetContextCreate macro.)
  */
 MemoryContext
-AllocSetContextCreate(MemoryContext parent,
-                                         const char *name,
-                                         Size minContextSize,
-                                         Size initBlockSize,
-                                         Size maxBlockSize)
+AllocSetContextCreateExtended(MemoryContext parent,
+                                                         const char *name,
+                                                         int flags,
+                                                         Size minContextSize,
+                                                         Size initBlockSize,
+                                                         Size maxBlockSize)
 {
+       int                     freeListIndex;
+       Size            headerSize;
+       Size            firstBlockSize;
        AllocSet        set;
+       AllocBlock      block;
 
        /* Assert we padded AllocChunkData properly */
        StaticAssertStmt(ALLOC_CHUNKHDRSZ == MAXALIGN(ALLOC_CHUNKHDRSZ),
@@ -355,36 +412,125 @@ AllocSetContextCreate(MemoryContext parent,
                                         "padding calculation in AllocChunkData 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.
+        * First, validate allocation parameters.  Once these were regular runtime
+        * test and elog's, but in practice Asserts seem sufficient because nobody
+        * varies their parameters at runtime.  We somewhat arbitrarily enforce a
+        * minimum 1K block size.
         */
-       if (initBlockSize != MAXALIGN(initBlockSize) ||
-               initBlockSize < 1024)
-               elog(ERROR, "invalid initBlockSize for memory context: %zu",
-                        initBlockSize);
-       if (maxBlockSize != MAXALIGN(maxBlockSize) ||
-               maxBlockSize < initBlockSize ||
-               !AllocHugeSizeIsValid(maxBlockSize))    /* must be safe to double */
-               elog(ERROR, "invalid maxBlockSize for memory context: %zu",
-                        maxBlockSize);
-       if (minContextSize != 0 &&
-               (minContextSize != MAXALIGN(minContextSize) ||
-                minContextSize <= ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ))
-               elog(ERROR, "invalid minContextSize for memory context: %zu",
-                        minContextSize);
-
-       /* Do the type-independent part of context creation */
-       set = (AllocSet) MemoryContextCreate(T_AllocSetContext,
-                                                                                sizeof(AllocSetContext),
-                                                                                &AllocSetMethods,
-                                                                                parent,
-                                                                                name);
-
-       /* Save allocation parameters */
+       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));
+
+       /*
+        * Check whether the parameters match either available freelist.  We do
+        * not need to demand a match of maxBlockSize.
+        */
+       if (flags == 0 &&
+               minContextSize == ALLOCSET_DEFAULT_MINSIZE &&
+               initBlockSize == ALLOCSET_DEFAULT_INITSIZE)
+               freeListIndex = 0;
+       else if (flags == 0 &&
+                        minContextSize == ALLOCSET_SMALL_MINSIZE &&
+                        initBlockSize == ALLOCSET_SMALL_INITSIZE)
+               freeListIndex = 1;
+       else
+               freeListIndex = -1;
+
+       /*
+        * If a suitable freelist entry exists, just recycle that context.
+        */
+       if (freeListIndex >= 0)
+       {
+               AllocSetFreeList *freelist = &context_freelists[freeListIndex];
+
+               if (freelist->first_free != NULL)
+               {
+                       /* Remove entry from freelist */
+                       set = freelist->first_free;
+                       freelist->first_free = (AllocSet) set->header.nextchild;
+                       freelist->num_free--;
+
+                       /* Update its maxBlockSize; everything else should be OK */
+                       set->maxBlockSize = maxBlockSize;
+
+                       /* Reinitialize its header, installing correct name and parent */
+                       MemoryContextCreate((MemoryContext) set,
+                                                               T_AllocSetContext,
+                                                               set->headerSize,
+                                                               sizeof(AllocSetContext),
+                                                               &AllocSetMethods,
+                                                               parent,
+                                                               name,
+                                                               flags);
+
+                       return (MemoryContext) set;
+               }
+       }
+
+       /* Size of the memory context header, including name storage if needed */
+       if (flags & MEMCONTEXT_COPY_NAME)
+               headerSize = MAXALIGN(sizeof(AllocSetContext) + strlen(name) + 1);
+       else
+               headerSize = MAXALIGN(sizeof(AllocSetContext));
+
+       /* Determine size of initial block */
+       firstBlockSize = headerSize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
+       if (minContextSize != 0)
+               firstBlockSize = Max(firstBlockSize, minContextSize);
+       else
+               firstBlockSize = Max(firstBlockSize, initBlockSize);
+
+       /*
+        * Allocate the initial block.  Unlike other aset.c blocks, it starts with
+        * the context header and its block header follows that.
+        */
+       set = (AllocSet) malloc(firstBlockSize);
+       if (set == NULL)
+       {
+               if (TopMemoryContext)
+                       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/initial block if we ereport in this stretch.
+        */
+
+       /* Fill in the initial block's block header */
+       block = (AllocBlock) (((char *) set) + headerSize);
+       block->aset = set;
+       block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
+       block->endptr = ((char *) set) + firstBlockSize;
+       block->prev = NULL;
+       block->next = NULL;
+
+       /* Mark unallocated space NOACCESS; leave the block header alone. */
+       VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, block->endptr - block->freeptr);
+
+       /* Remember block as part of block list */
+       set->blocks = block;
+       /* Mark block as not to be released at reset time */
+       set->keeper = block;
+
+       /* Finish filling in aset-specific parts of the context header */
+       MemSetAligned(set->freelist, 0, sizeof(set->freelist));
+
        set->initBlockSize = initBlockSize;
        set->maxBlockSize = maxBlockSize;
        set->nextBlockSize = initBlockSize;
+       set->headerSize = headerSize;
+       set->freeListIndex = freeListIndex;
 
        /*
         * Compute the allocation chunk size limit for this context.  It can't be
@@ -410,64 +556,19 @@ AllocSetContextCreate(MemoryContext parent,
                   (Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION))
                set->allocChunkLimit >>= 1;
 
-       /*
-        * Grab always-allocated space, if requested
-        */
-       if (minContextSize > 0)
-       {
-               Size            blksize = minContextSize;
-               AllocBlock      block;
-
-               block = (AllocBlock) malloc(blksize);
-               if (block == NULL)
-               {
-                       MemoryContextStats(TopMemoryContext);
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_OUT_OF_MEMORY),
-                                        errmsg("out of memory"),
-                                        errdetail("Failed while creating memory context \"%s\".",
-                                                          name)));
-               }
-               block->aset = set;
-               block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
-               block->endptr = ((char *) block) + blksize;
-               block->prev = NULL;
-               block->next = set->blocks;
-               if (block->next)
-                       block->next->prev = block;
-               set->blocks = block;
-               /* Mark block as not to be released at reset time */
-               set->keeper = block;
-
-               /* Mark unallocated space NOACCESS; leave the block header alone. */
-               VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
-                                                                  blksize - ALLOC_BLOCKHDRSZ);
-       }
+       /* Finally, do the type-independent part of context creation */
+       MemoryContextCreate((MemoryContext) set,
+                                               T_AllocSetContext,
+                                               headerSize,
+                                               sizeof(AllocSetContext),
+                                               &AllocSetMethods,
+                                               parent,
+                                               name,
+                                               flags);
 
        return (MemoryContext) set;
 }
 
-/*
- * AllocSetInit
- *             Context-type-specific initialization routine.
- *
- * This is called by MemoryContextCreate() after setting up the
- * generic MemoryContext fields and before linking the new context
- * into the context tree.  We must do whatever is needed to make the
- * new context minimally valid for deletion.  We must *not* risk
- * failure --- thus, for example, allocating more memory is not cool.
- * (AllocSetContextCreate can allocate memory when it gets control
- * back, however.)
- */
-static void
-AllocSetInit(MemoryContext context)
-{
-       /*
-        * Since MemoryContextCreate already zeroed the context node, we don't
-        * have to do anything here: it's already OK.
-        */
-}
-
 /*
  * AllocSetReset
  *             Frees all memory which is allocated in the given set.
@@ -475,9 +576,10 @@ AllocSetInit(MemoryContext context)
  * Actually, this routine has some discretion about what to do.
  * It should mark all allocated chunks freed, but it need not necessarily
  * give back all the resources the set owns.  Our actual implementation is
- * that we hang onto any "keeper" block specified for the set.  In this way,
- * we don't thrash malloc() when a context is repeatedly reset after small
- * allocations, which is typical behavior for per-tuple contexts.
+ * that we give back all but the "keeper" block (which we must keep, since
+ * it shares a malloc chunk with the context header).  In this way, we don't
+ * thrash malloc() when a context is repeatedly reset after small allocations,
+ * which is typical behavior for per-tuple contexts.
  */
 static void
 AllocSetReset(MemoryContext context)
@@ -497,7 +599,7 @@ AllocSetReset(MemoryContext context)
 
        block = set->blocks;
 
-       /* New blocks list is either empty or just the keeper block */
+       /* New blocks list will be just the keeper block */
        set->blocks = set->keeper;
 
        while (block != NULL)
@@ -540,7 +642,6 @@ AllocSetReset(MemoryContext context)
  *             in preparation for deletion of the set.
  *
  * Unlike AllocSetReset, this *must* free all resources of the set.
- * But note we are not responsible for deleting the context node itself.
  */
 static void
 AllocSetDelete(MemoryContext context)
@@ -555,11 +656,49 @@ AllocSetDelete(MemoryContext context)
        AllocSetCheck(context);
 #endif
 
-       /* Make it look empty, just in case... */
-       MemSetAligned(set->freelist, 0, sizeof(set->freelist));
-       set->blocks = NULL;
-       set->keeper = NULL;
+       /*
+        * If the context is a candidate for a freelist, put it into that freelist
+        * instead of destroying it.
+        */
+       if (set->freeListIndex >= 0)
+       {
+               AllocSetFreeList *freelist = &context_freelists[set->freeListIndex];
+
+               /*
+                * Reset the context, if it needs it, so that we aren't hanging on to
+                * more than the initial malloc chunk.
+                */
+               if (!context->isReset)
+                       MemoryContextResetOnly(context);
+
+               /*
+                * If the freelist is full, just discard what's already in it.  See
+                * comments with context_freelists[].
+                */
+               if (freelist->num_free >= MAX_FREE_CONTEXTS)
+               {
+                       while (freelist->first_free != NULL)
+                       {
+                               AllocSetContext *oldset = freelist->first_free;
+
+                               freelist->first_free = (AllocSetContext *) oldset->header.nextchild;
+                               freelist->num_free--;
+
+                               /* All that remains is to free the header/initial block */
+                               free(oldset);
+                       }
+                       Assert(freelist->num_free == 0);
+               }
+
+               /* Now add the just-deleted context to the freelist. */
+               set->header.nextchild = (MemoryContext) freelist->first_free;
+               freelist->first_free = set;
+               freelist->num_free++;
+
+               return;
+       }
 
+       /* Free all blocks, except the keeper which is part of context header */
        while (block != NULL)
        {
                AllocBlock      next = block->next;
@@ -567,9 +706,15 @@ AllocSetDelete(MemoryContext context)
 #ifdef CLOBBER_FREED_MEMORY
                wipe_mem(block, block->freeptr - ((char *) block));
 #endif
-               free(block);
+
+               if (block != set->keeper)
+                       free(block);
+
                block = next;
        }
+
+       /* Finally, free the context header, including the keeper block */
+       free(set);
 }
 
 /*
@@ -807,18 +952,6 @@ AllocSetAlloc(MemoryContext context, Size size)
                block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
                block->endptr = ((char *) block) + blksize;
 
-               /*
-                * If this is the first block of the set, make it the "keeper" block.
-                * Formerly, a keeper block could only be created during context
-                * creation, but allowing it to happen here lets us have fast reset
-                * cycling even for contexts created with minContextSize = 0; that way
-                * we don't have to force space to be allocated in contexts that might
-                * never need any space.  Don't mark an oversize block as a keeper,
-                * however.
-                */
-               if (set->keeper == NULL && blksize == set->initBlockSize)
-                       set->keeper = block;
-
                /* Mark unallocated space NOACCESS. */
                VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
                                                                   blksize - ALLOC_BLOCKHDRSZ);
@@ -1205,11 +1338,14 @@ AllocSetStats(MemoryContext context, int level, bool print,
        AllocSet        set = (AllocSet) context;
        Size            nblocks = 0;
        Size            freechunks = 0;
-       Size            totalspace = 0;
+       Size            totalspace;
        Size            freespace = 0;
        AllocBlock      block;
        int                     fidx;
 
+       /* Include context header in totalspace */
+       totalspace = set->headerSize;
+
        for (block = set->blocks; block != NULL; block = block->next)
        {
                nblocks++;
@@ -1264,7 +1400,7 @@ static void
 AllocSetCheck(MemoryContext context)
 {
        AllocSet        set = (AllocSet) context;
-       char       *name = set->header.name;
+       const char *name = set->header.name;
        AllocBlock      prevblock;
        AllocBlock      block;
 
index 19390fa58185728d432a007a52ba92c361e3b043..10d0fc1f904becc406c18102560b29816c8a7bad 100644 (file)
@@ -61,6 +61,7 @@ typedef struct GenerationContext
 
        /* Generational context parameters */
        Size            blockSize;              /* standard block size */
+       Size            headerSize;             /* allocated size of context header */
 
        GenerationBlock *block;         /* current (most recently allocated) block */
        dlist_head      blocks;                 /* list of blocks */
@@ -149,7 +150,6 @@ struct GenerationChunk
 static void *GenerationAlloc(MemoryContext context, Size size);
 static void GenerationFree(MemoryContext context, void *pointer);
 static void *GenerationRealloc(MemoryContext context, void *pointer, Size size);
-static void GenerationInit(MemoryContext context);
 static void GenerationReset(MemoryContext context);
 static void GenerationDelete(MemoryContext context);
 static Size GenerationGetChunkSpace(MemoryContext context, void *pointer);
@@ -164,11 +164,10 @@ static void GenerationCheck(MemoryContext context);
 /*
  * This is the virtual function table for Generation contexts.
  */
-static MemoryContextMethods GenerationMethods = {
+static const MemoryContextMethods GenerationMethods = {
        GenerationAlloc,
        GenerationFree,
        GenerationRealloc,
-       GenerationInit,
        GenerationReset,
        GenerationDelete,
        GenerationGetChunkSpace,
@@ -208,8 +207,10 @@ static MemoryContextMethods GenerationMethods = {
 MemoryContext
 GenerationContextCreate(MemoryContext parent,
                                                const char *name,
+                                               int flags,
                                                Size blockSize)
 {
+       Size            headerSize;
        GenerationContext *set;
 
        /* Assert we padded GenerationChunk properly */
@@ -231,29 +232,51 @@ GenerationContextCreate(MemoryContext parent,
                elog(ERROR, "invalid blockSize for memory context: %zu",
                         blockSize);
 
-       /* Do the type-independent part of context creation */
-       set = (GenerationContext *) MemoryContextCreate(T_GenerationContext,
-                                                                                                       sizeof(GenerationContext),
-                                                                                                       &GenerationMethods,
-                                                                                                       parent,
-                                                                                                       name);
+       /*
+        * 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.
+        */
 
-       set->blockSize = blockSize;
+       /* Size of the memory context header, including name storage if needed */
+       if (flags & MEMCONTEXT_COPY_NAME)
+               headerSize = MAXALIGN(sizeof(GenerationContext) + strlen(name) + 1);
+       else
+               headerSize = MAXALIGN(sizeof(GenerationContext));
 
-       return (MemoryContext) set;
-}
+       set = (GenerationContext *) malloc(headerSize);
+       if (set == NULL)
+       {
+               MemoryContextStats(TopMemoryContext);
+               ereport(ERROR,
+                               (errcode(ERRCODE_OUT_OF_MEMORY),
+                                errmsg("out of memory"),
+                                errdetail("Failed while creating memory context \"%s\".",
+                                                  name)));
+       }
 
-/*
- * GenerationInit
- *             Context-type-specific initialization routine.
- */
-static void
-GenerationInit(MemoryContext context)
-{
-       GenerationContext *set = (GenerationContext *) context;
+       /*
+        * Avoid writing code that can fail between here and MemoryContextCreate;
+        * we'd leak the header if we ereport in this stretch.
+        */
 
+       /* Fill in GenerationContext-specific header fields */
+       set->blockSize = blockSize;
+       set->headerSize = headerSize;
        set->block = NULL;
        dlist_init(&set->blocks);
+
+       /* Finally, do the type-independent part of context creation */
+       MemoryContextCreate((MemoryContext) set,
+                                               T_GenerationContext,
+                                               headerSize,
+                                               sizeof(GenerationContext),
+                                               &GenerationMethods,
+                                               parent,
+                                               name,
+                                               flags);
+
+       return (MemoryContext) set;
 }
 
 /*
@@ -296,16 +319,15 @@ GenerationReset(MemoryContext context)
 
 /*
  * GenerationDelete
- *             Frees all memory which is allocated in the given set, in preparation
- *             for deletion of the set. We simply call GenerationReset() which does
- *             all the dirty work.
+ *             Free all memory which is allocated in the given context.
  */
 static void
 GenerationDelete(MemoryContext context)
 {
-       /* just reset to release all the GenerationBlocks */
+       /* Reset to release all the GenerationBlocks */
        GenerationReset(context);
-       /* we are not responsible for deleting the context node itself */
+       /* And free the context header */
+       free(context);
 }
 
 /*
@@ -659,7 +681,7 @@ GenerationIsEmpty(MemoryContext context)
 
 /*
  * GenerationStats
- *             Compute stats about memory consumption of an Generation.
+ *             Compute stats about memory consumption of a Generation context.
  *
  * level: recursion level (0 at top level); used for print indentation.
  * print: true to print stats to stderr.
@@ -676,10 +698,13 @@ GenerationStats(MemoryContext context, int level, bool print,
        Size            nblocks = 0;
        Size            nchunks = 0;
        Size            nfreechunks = 0;
-       Size            totalspace = 0;
+       Size            totalspace;
        Size            freespace = 0;
        dlist_iter      iter;
 
+       /* Include context header in totalspace */
+       totalspace = set->headerSize;
+
        dlist_foreach(iter, &set->blocks)
        {
                GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
@@ -727,7 +752,7 @@ static void
 GenerationCheck(MemoryContext context)
 {
        GenerationContext *gen = (GenerationContext *) context;
-       char       *name = context->name;
+       const char *name = context->name;
        dlist_iter      iter;
 
        /* walk all blocks in this context */
index c5c311fad39c8f96eb03c3d4798574670a5e7f3e..97382a693c3e7a439aa2b73df7f2c1226450b49d 100644 (file)
@@ -91,9 +91,7 @@ MemoryContextInit(void)
        AssertState(TopMemoryContext == NULL);
 
        /*
-        * First, initialize TopMemoryContext, which will hold the MemoryContext
-        * nodes for all other contexts.  (There is special-case code in
-        * MemoryContextCreate() to handle this call.)
+        * First, initialize TopMemoryContext, which is the parent of all others.
         */
        TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL,
                                                                                         "TopMemoryContext",
@@ -118,11 +116,12 @@ MemoryContextInit(void)
         * This should be the last step in this function, as elog.c assumes memory
         * management works once ErrorContext is non-null.
         */
-       ErrorContext = AllocSetContextCreate(TopMemoryContext,
-                                                                                "ErrorContext",
-                                                                                8 * 1024,
-                                                                                8 * 1024,
-                                                                                8 * 1024);
+       ErrorContext = AllocSetContextCreateExtended(TopMemoryContext,
+                                                                                                "ErrorContext",
+                                                                                                0,
+                                                                                                8 * 1024,
+                                                                                                8 * 1024,
+                                                                                                8 * 1024);
        MemoryContextAllowInCriticalSection(ErrorContext, true);
 }
 
@@ -191,10 +190,9 @@ MemoryContextResetChildren(MemoryContext context)
  *             Delete a context and its descendants, and release all space
  *             allocated therein.
  *
- * The type-specific delete routine removes all subsidiary storage
- * for the context, but we have to delete the context node itself,
- * as well as recurse to get the children.  We must also delink the
- * node from its parent, if it has one.
+ * The type-specific delete routine removes all storage for the context,
+ * but we have to recurse to handle the children.
+ * We must also delink the context from its parent, if it has one.
  */
 void
 MemoryContextDelete(MemoryContext context)
@@ -205,7 +203,9 @@ MemoryContextDelete(MemoryContext context)
        /* And not CurrentMemoryContext, either */
        Assert(context != CurrentMemoryContext);
 
-       MemoryContextDeleteChildren(context);
+       /* save a function call in common case where there are no children */
+       if (context->firstchild != NULL)
+               MemoryContextDeleteChildren(context);
 
        /*
         * It's not entirely clear whether 'tis better to do this before or after
@@ -223,8 +223,8 @@ MemoryContextDelete(MemoryContext context)
        MemoryContextSetParent(context, NULL);
 
        context->methods->delete_context(context);
+
        VALGRIND_DESTROY_MEMPOOL(context);
-       pfree(context);
 }
 
 /*
@@ -587,100 +587,85 @@ MemoryContextContains(MemoryContext context, void *pointer)
        return ptr_context == context;
 }
 
-/*--------------------
+/*
  * MemoryContextCreate
  *             Context-type-independent part of context creation.
  *
  * This is only intended to be called by context-type-specific
  * context creation routines, not by the unwashed masses.
  *
- * The context creation procedure is a little bit tricky because
- * we want to be sure that we don't leave the context tree invalid
- * in case of failure (such as insufficient memory to allocate the
- * context node itself).  The procedure goes like this:
- *     1.  Context-type-specific routine first calls MemoryContextCreate(),
- *             passing the appropriate tag/size/methods values (the methods
- *             pointer will ordinarily point to statically allocated data).
- *             The parent and name parameters usually come from the caller.
- *     2.  MemoryContextCreate() attempts to allocate the context node,
- *             plus space for the name.  If this fails we can ereport() with no
- *             damage done.
- *     3.  We fill in all of the type-independent MemoryContext fields.
- *     4.  We call the type-specific init routine (using the methods pointer).
- *             The init routine is required to make the node minimally valid
- *             with zero chance of failure --- it can't allocate more memory,
- *             for example.
- *     5.  Now we have a minimally valid node that can behave correctly
- *             when told to reset or delete itself.  We link the node to its
- *             parent (if any), making the node part of the context tree.
- *     6.  We return to the context-type-specific routine, which finishes
+ * The memory context creation procedure goes like this:
+ *     1.  Context-type-specific routine makes some initial space allocation,
+ *             including enough space for the context header.  If it fails,
+ *             it can ereport() with no damage done.
+ *     2.      Context-type-specific routine sets up all type-specific fields of
+ *             the header (those beyond MemoryContextData proper), as well as any
+ *             other management fields it needs to have a fully valid context.
+ *             Usually, failure in this step is impossible, but if it's possible
+ *             the initial space allocation should be freed before ereport'ing.
+ *     3.      Context-type-specific routine calls MemoryContextCreate() to fill in
+ *             the generic header fields and link the context into the context tree.
+ *     4.  We return to the context-type-specific routine, which finishes
  *             up type-specific initialization.  This routine can now do things
  *             that might fail (like allocate more memory), so long as it's
  *             sure the node is left in a state that delete will handle.
  *
- * This protocol doesn't prevent us from leaking memory if step 6 fails
- * during creation of a top-level context, since there's no parent link
- * in that case.  However, if you run out of memory while you're building
- * a top-level context, you might as well go home anyway...
- *
- * Normally, the context node and the name are allocated from
- * TopMemoryContext (NOT from the parent context, since the node must
- * survive resets of its parent context!).  However, this routine is itself
- * used to create TopMemoryContext!  If we see that TopMemoryContext is NULL,
- * we assume we are creating TopMemoryContext and use malloc() to allocate
- * the node.
+ * node: the as-yet-uninitialized common part of the context header node.
+ * tag: NodeTag code identifying the memory context type.
+ * size: total size of context header including context-type-specific fields,
+ *             as well as space for the context name if MEMCONTEXT_COPY_NAME is set.
+ * nameoffset: where within the "size" space to insert the context name.
+ * methods: context-type-specific methods (usually statically allocated).
+ * parent: parent context, or NULL if this will be a top-level context.
+ * name: name of context (for debugging only, need not be unique).
+ * flags: bitmask of MEMCONTEXT_XXX option flags.
  *
- * Note that the name field of a MemoryContext does not point to
- * separately-allocated storage, so it should not be freed at context
- * deletion.
- *--------------------
+ * Context routines generally assume that MemoryContextCreate can't fail,
+ * so this can contain Assert but not elog/ereport.
  */
-MemoryContext
-MemoryContextCreate(NodeTag tag, Size size,
-                                       MemoryContextMethods *methods,
+void
+MemoryContextCreate(MemoryContext node,
+                                       NodeTag tag, Size size, Size nameoffset,
+                                       const MemoryContextMethods *methods,
                                        MemoryContext parent,
-                                       const char *name)
+                                       const char *name,
+                                       int flags)
 {
-       MemoryContext node;
-       Size            needed = size + strlen(name) + 1;
-
-       /* creating new memory contexts is not allowed in a critical section */
+       /* Creating new memory contexts is not allowed in a critical section */
        Assert(CritSectionCount == 0);
 
-       /* Get space for node and name */
-       if (TopMemoryContext != NULL)
-       {
-               /* Normal case: allocate the node in TopMemoryContext */
-               node = (MemoryContext) MemoryContextAlloc(TopMemoryContext,
-                                                                                                 needed);
-       }
-       else
-       {
-               /* Special case for startup: use good ol' malloc */
-               node = (MemoryContext) malloc(needed);
-               Assert(node != NULL);
-       }
+       /* Check size is sane */
+       Assert(nameoffset >= sizeof(MemoryContextData));
+       Assert((flags & MEMCONTEXT_COPY_NAME) ?
+                  size >= nameoffset + strlen(name) + 1 :
+                  size >= nameoffset);
 
-       /* Initialize the node as best we can */
-       MemSet(node, 0, size);
+       /* Initialize all standard fields of memory context header */
        node->type = tag;
+       node->isReset = true;
        node->methods = methods;
-       node->parent = NULL;            /* for the moment */
+       node->parent = parent;
        node->firstchild = NULL;
        node->prevchild = NULL;
-       node->nextchild = NULL;
-       node->isReset = true;
-       node->name = ((char *) node) + size;
-       strcpy(node->name, name);
+       node->reset_cbs = NULL;
 
-       /* Type-specific routine finishes any other essential initialization */
-       node->methods->init(node);
+       if (flags & MEMCONTEXT_COPY_NAME)
+       {
+               /* Insert context name into space reserved for it */
+               char       *namecopy = ((char *) node) + nameoffset;
 
-       /* OK to link node to parent (if any) */
-       /* Could use MemoryContextSetParent here, but doesn't seem worthwhile */
+               node->name = namecopy;
+               strcpy(namecopy, name);
+       }
+       else
+       {
+               /* Assume the passed-in name is statically allocated */
+               node->name = name;
+       }
+
+       /* OK to link node into context tree */
        if (parent)
        {
-               node->parent = parent;
                node->nextchild = parent->firstchild;
                if (parent->firstchild != NULL)
                        parent->firstchild->prevchild = node;
@@ -688,11 +673,13 @@ MemoryContextCreate(NodeTag tag, Size size,
                /* inherit allowInCritSection flag from parent */
                node->allowInCritSection = parent->allowInCritSection;
        }
+       else
+       {
+               node->nextchild = NULL;
+               node->allowInCritSection = false;
+       }
 
        VALGRIND_CREATE_MEMPOOL(node, 0, false);
-
-       /* Return to type-specific creation routine to finish up */
-       return node;
 }
 
 /*
index ee2175278d2e1ed4a064ccde8f1fdf089ffdedda..c01c77913a8c7fdaabc6a43f8b2d9d6e28509e55 100644 (file)
@@ -67,6 +67,7 @@ typedef struct SlabContext
        Size            chunkSize;              /* chunk size */
        Size            fullChunkSize;  /* chunk size including header and alignment */
        Size            blockSize;              /* block size */
+       Size            headerSize;             /* allocated size of context header */
        int                     chunksPerBlock; /* number of chunks per block */
        int                     minFreeChunks;  /* min number of free chunks in any block */
        int                     nblocks;                /* number of blocks allocated */
@@ -126,7 +127,6 @@ typedef struct SlabChunk
 static void *SlabAlloc(MemoryContext context, Size size);
 static void SlabFree(MemoryContext context, void *pointer);
 static void *SlabRealloc(MemoryContext context, void *pointer, Size size);
-static void SlabInit(MemoryContext context);
 static void SlabReset(MemoryContext context);
 static void SlabDelete(MemoryContext context);
 static Size SlabGetChunkSpace(MemoryContext context, void *pointer);
@@ -140,11 +140,10 @@ static void SlabCheck(MemoryContext context);
 /*
  * This is the virtual function table for Slab contexts.
  */
-static MemoryContextMethods SlabMethods = {
+static const MemoryContextMethods SlabMethods = {
        SlabAlloc,
        SlabFree,
        SlabRealloc,
-       SlabInit,
        SlabReset,
        SlabDelete,
        SlabGetChunkSpace,
@@ -177,24 +176,30 @@ static MemoryContextMethods SlabMethods = {
  *             Create a new Slab context.
  *
  * parent: parent context, or NULL if top-level context
- * name: name of context (for debugging --- string will be copied)
+ * name: name of context (for debugging only, need not be unique)
+ * flags: bitmask of MEMCONTEXT_XXX option flags
  * blockSize: allocation block size
  * chunkSize: allocation chunk size
  *
+ * Notes: if flags & MEMCONTEXT_COPY_NAME, the name string will be copied into
+ * context-lifespan storage; otherwise, it had better be statically allocated.
  * The chunkSize may not exceed:
  *             MAXALIGN_DOWN(SIZE_MAX) - MAXALIGN(sizeof(SlabBlock)) - SLAB_CHUNKHDRSZ
- *
  */
 MemoryContext
 SlabContextCreate(MemoryContext parent,
                                  const char *name,
+                                 int flags,
                                  Size blockSize,
                                  Size chunkSize)
 {
        int                     chunksPerBlock;
        Size            fullChunkSize;
        Size            freelistSize;
+       Size            nameOffset;
+       Size            headerSize;
        SlabContext *slab;
+       int                     i;
 
        /* Assert we padded SlabChunk properly */
        StaticAssertStmt(sizeof(SlabChunk) == MAXALIGN(sizeof(SlabChunk)),
@@ -211,7 +216,7 @@ SlabContextCreate(MemoryContext parent,
        fullChunkSize = sizeof(SlabChunk) + MAXALIGN(chunkSize);
 
        /* Make sure the block can store at least one chunk. */
-       if (blockSize - sizeof(SlabBlock) < fullChunkSize)
+       if (blockSize < fullChunkSize + sizeof(SlabBlock))
                elog(ERROR, "block size %zu for slab is too small for %zu chunks",
                         blockSize, chunkSize);
 
@@ -221,45 +226,58 @@ SlabContextCreate(MemoryContext parent,
        /* The freelist starts with 0, ends with chunksPerBlock. */
        freelistSize = sizeof(dlist_head) * (chunksPerBlock + 1);
 
-       /* if we can't fit at least one chunk into the block, we're hosed */
-       Assert(chunksPerBlock > 0);
+       /*
+        * Allocate the context header.  Unlike aset.c, we never try to combine
+        * this with the first regular block; not worth the extra complication.
+        */
 
-       /* make sure the chunks actually fit on the block       */
-       Assert((fullChunkSize * chunksPerBlock) + sizeof(SlabBlock) <= blockSize);
+       /* Size of the memory context header, including name storage if needed */
+       nameOffset = offsetof(SlabContext, freelist) + freelistSize;
+       if (flags & MEMCONTEXT_COPY_NAME)
+               headerSize = nameOffset + strlen(name) + 1;
+       else
+               headerSize = nameOffset;
 
-       /* Do the type-independent part of context creation */
-       slab = (SlabContext *)
-               MemoryContextCreate(T_SlabContext,
-                                                       (offsetof(SlabContext, freelist) + freelistSize),
-                                                       &SlabMethods,
-                                                       parent,
-                                                       name);
+       slab = (SlabContext *) malloc(headerSize);
+       if (slab == NULL)
+       {
+               MemoryContextStats(TopMemoryContext);
+               ereport(ERROR,
+                               (errcode(ERRCODE_OUT_OF_MEMORY),
+                                errmsg("out of memory"),
+                                errdetail("Failed while creating memory context \"%s\".",
+                                                  name)));
+       }
 
-       slab->blockSize = blockSize;
+       /*
+        * Avoid writing code that can fail between here and MemoryContextCreate;
+        * we'd leak the header if we ereport in this stretch.
+        */
+
+       /* Fill in SlabContext-specific header fields */
        slab->chunkSize = chunkSize;
        slab->fullChunkSize = fullChunkSize;
+       slab->blockSize = blockSize;
+       slab->headerSize = headerSize;
        slab->chunksPerBlock = chunksPerBlock;
-       slab->nblocks = 0;
        slab->minFreeChunks = 0;
-
-       return (MemoryContext) slab;
-}
-
-/*
- * SlabInit
- *             Context-type-specific initialization routine.
- */
-static void
-SlabInit(MemoryContext context)
-{
-       int                     i;
-       SlabContext *slab = castNode(SlabContext, context);
-
-       Assert(slab);
+       slab->nblocks = 0;
 
        /* initialize the freelist slots */
        for (i = 0; i < (slab->chunksPerBlock + 1); i++)
                dlist_init(&slab->freelist[i]);
+
+       /* Finally, do the type-independent part of context creation */
+       MemoryContextCreate((MemoryContext) slab,
+                                               T_SlabContext,
+                                               headerSize,
+                                               nameOffset,
+                                               &SlabMethods,
+                                               parent,
+                                               name,
+                                               flags);
+
+       return (MemoryContext) slab;
 }
 
 /*
@@ -308,14 +326,15 @@ SlabReset(MemoryContext context)
 
 /*
  * SlabDelete
- *             Frees all memory which is allocated in the given slab, in preparation
- *             for deletion of the slab. We simply call SlabReset().
+ *             Free all memory which is allocated in the given context.
  */
 static void
 SlabDelete(MemoryContext context)
 {
-       /* just reset the context */
+       /* Reset to release all the SlabBlocks */
        SlabReset(context);
+       /* And free the context header */
+       free(context);
 }
 
 /*
@@ -613,7 +632,7 @@ SlabIsEmpty(MemoryContext context)
 
 /*
  * SlabStats
- *             Compute stats about memory consumption of an Slab.
+ *             Compute stats about memory consumption of a Slab context.
  *
  * level: recursion level (0 at top level); used for print indentation.
  * print: true to print stats to stderr.
@@ -626,11 +645,12 @@ SlabStats(MemoryContext context, int level, bool print,
        SlabContext *slab = castNode(SlabContext, context);
        Size            nblocks = 0;
        Size            freechunks = 0;
-       Size            totalspace = 0;
+       Size            totalspace;
        Size            freespace = 0;
        int                     i;
 
-       Assert(slab);
+       /* Include context header in totalspace */
+       totalspace = slab->headerSize;
 
        for (i = 0; i <= slab->chunksPerBlock; i++)
        {
@@ -682,7 +702,7 @@ SlabCheck(MemoryContext context)
 {
        int                     i;
        SlabContext *slab = castNode(SlabContext, context);
-       char       *name = slab->header.name;
+       const char *name = slab->header.name;
        char       *freechunks;
 
        Assert(slab);
index e22d9fb17816215144e2a923211aad2fd82e8ce3..c7eb1e72e91cc8394045ea67eb14ff49df338082 100644 (file)
@@ -57,7 +57,6 @@ typedef struct MemoryContextMethods
        /* call this free_p in case someone #define's free() */
        void            (*free_p) (MemoryContext context, void *pointer);
        void       *(*realloc) (MemoryContext context, void *pointer, Size size);
-       void            (*init) (MemoryContext context);
        void            (*reset) (MemoryContext context);
        void            (*delete_context) (MemoryContext context);
        Size            (*get_chunk_space) (MemoryContext context, void *pointer);
@@ -76,12 +75,12 @@ typedef struct MemoryContextData
        /* these two fields are placed here to minimize alignment wastage: */
        bool            isReset;                /* T = no space alloced since last reset */
        bool            allowInCritSection; /* allow palloc in critical section */
-       MemoryContextMethods *methods;  /* virtual function table */
+       const MemoryContextMethods *methods;    /* virtual function table */
        MemoryContext parent;           /* NULL if no parent (toplevel context) */
        MemoryContext firstchild;       /* head of linked list of children */
        MemoryContext prevchild;        /* previous child of same parent */
        MemoryContext nextchild;        /* next child of same parent */
-       char       *name;                       /* context name (just for debugging) */
+       const char *name;                       /* context name (just for debugging) */
        MemoryContextCallback *reset_cbs;       /* list of reset/delete callbacks */
 } MemoryContextData;
 
index d177b0cc8dbcce8ed3c3c3007b6b31f78fe82370..9c30eb76e9963a39daa171cbd1e7441f171a6639 100644 (file)
@@ -132,10 +132,12 @@ GetMemoryChunkContext(void *pointer)
  * context creation.  It's intended to be called from context-type-
  * specific creation routines, and noplace else.
  */
-extern MemoryContext MemoryContextCreate(NodeTag tag, Size size,
-                                       MemoryContextMethods *methods,
+extern void MemoryContextCreate(MemoryContext node,
+                                       NodeTag tag, Size size, Size nameoffset,
+                                       const MemoryContextMethods *methods,
                                        MemoryContext parent,
-                                       const char *name);
+                                       const char *name,
+                                       int flags);
 
 
 /*
@@ -143,23 +145,48 @@ extern MemoryContext MemoryContextCreate(NodeTag tag, Size size,
  */
 
 /* aset.c */
-extern MemoryContext AllocSetContextCreate(MemoryContext parent,
-                                         const char *name,
-                                         Size minContextSize,
-                                         Size initBlockSize,
-                                         Size maxBlockSize);
+extern MemoryContext AllocSetContextCreateExtended(MemoryContext parent,
+                                                         const char *name,
+                                                         int flags,
+                                                         Size minContextSize,
+                                                         Size initBlockSize,
+                                                         Size maxBlockSize);
+
+/*
+ * This backwards compatibility macro only works for constant context names,
+ * and you must specify block sizes with one of the abstraction macros below.
+ */
+#ifdef HAVE__BUILTIN_CONSTANT_P
+#define AllocSetContextCreate(parent, name, allocparams) \
+       (StaticAssertExpr(__builtin_constant_p(name), \
+                                         "Use AllocSetContextCreateExtended with MEMCONTEXT_COPY_NAME for non-constant context names"), \
+        AllocSetContextCreateExtended(parent, name, 0, allocparams))
+#else
+#define AllocSetContextCreate(parent, name, allocparams) \
+       AllocSetContextCreateExtended(parent, name, 0, allocparams)
+#endif
 
 /* slab.c */
 extern MemoryContext SlabContextCreate(MemoryContext parent,
                                  const char *name,
+                                 int flags,
                                  Size blockSize,
                                  Size chunkSize);
 
 /* generation.c */
 extern MemoryContext GenerationContextCreate(MemoryContext parent,
                                                const char *name,
+                                               int flags,
                                                Size blockSize);
 
+/*
+ * Flag option bits for FooContextCreate functions.
+ * In future, some of these might be relevant to only some context types.
+ *
+ * COPY_NAME: FooContextCreate's name argument is not a constant string
+ */
+#define MEMCONTEXT_COPY_NAME           0x0001  /* is passed name transient? */
+
 /*
  * Recommended default alloc parameters, suitable for "ordinary" contexts
  * that might hold quite a lot of data.
index 9f5313235f344c772794b3929b1a3878af7d17e1..41fd0ba42148f3836514b943948eb778343aa6a6 100644 (file)
@@ -2777,9 +2777,10 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
                /************************************************************
                 * Allocate a context that will hold all PG data for the procedure.
                 ************************************************************/
-               proc_cxt = AllocSetContextCreate(TopMemoryContext,
-                                                                                NameStr(procStruct->proname),
-                                                                                ALLOCSET_SMALL_SIZES);
+               proc_cxt = AllocSetContextCreateExtended(TopMemoryContext,
+                                                                                                NameStr(procStruct->proname),
+                                                                                                MEMCONTEXT_COPY_NAME,
+                                                                                                ALLOCSET_SMALL_SIZES);
 
                /************************************************************
                 * Allocate and fill a new procedure description block.
index b7c24e356f550b443047d5b0c1a2807cb0d97b81..990a33cc6d20598518d1e346140fd0d65ec44ce7 100644 (file)
@@ -166,9 +166,10 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
        }
 
        /* Create long-lived context that all procedure info will live in */
-       cxt = AllocSetContextCreate(TopMemoryContext,
-                                                               procName,
-                                                               ALLOCSET_DEFAULT_SIZES);
+       cxt = AllocSetContextCreateExtended(TopMemoryContext,
+                                                                               procName,
+                                                                               MEMCONTEXT_COPY_NAME,
+                                                                               ALLOCSET_DEFAULT_SIZES);
 
        oldcxt = MemoryContextSwitchTo(cxt);
 
index e0792d93e1c7d751b6824f6f54ecff1d40e54709..80697841516c34e09a99b6e5d413e60a4167b29c 100644 (file)
@@ -146,7 +146,7 @@ typedef struct pltcl_proc_desc
        Oid                     result_typid;   /* OID of fn's result type */
        FmgrInfo        result_in_func; /* input function for fn's result type */
        Oid                     result_typioparam;      /* param to pass to same */
-       bool            fn_is_procedure;/* true if this is a procedure */
+       bool            fn_is_procedure;        /* true if this is a procedure */
        bool            fn_retisset;    /* true if function returns a set */
        bool            fn_retistuple;  /* true if function returns composite */
        bool            fn_retisdomain; /* true if function returns domain */
@@ -1471,9 +1471,10 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
                 * Allocate a context that will hold all PG data for the procedure.
                 * We use the internal proc name as the context name.
                 ************************************************************/
-               proc_cxt = AllocSetContextCreate(TopMemoryContext,
-                                                                                internal_proname,
-                                                                                ALLOCSET_SMALL_SIZES);
+               proc_cxt = AllocSetContextCreateExtended(TopMemoryContext,
+                                                                                                internal_proname,
+                                                                                                MEMCONTEXT_COPY_NAME,
+                                                                                                ALLOCSET_SMALL_SIZES);
 
                /************************************************************
                 * Allocate and fill a new procedure description block.