summaryrefslogtreecommitdiff
path: root/src/backend/utils/mmgr/mcxt.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2017-12-13 13:55:12 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2017-12-13 13:55:16 -0500
commit9fa6f00b1308dd10da4eca2f31ccbfc7b35bb461 (patch)
tree8d90b8c780dc03890a2b638b3058c44ee26700d7 /src/backend/utils/mmgr/mcxt.c
parent632b03da31cbbf4d32193d35031d301bd50d2679 (diff)
Rethink MemoryContext creation to improve performance.
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
Diffstat (limited to 'src/backend/utils/mmgr/mcxt.c')
-rw-r--r--src/backend/utils/mmgr/mcxt.c159
1 files changed, 73 insertions, 86 deletions
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index c5c311fad39..97382a693c3 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -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;
}
/*