diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2017-12-13 13:55:12 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2017-12-13 13:55:16 -0500 |
commit | 9fa6f00b1308dd10da4eca2f31ccbfc7b35bb461 (patch) | |
tree | 8d90b8c780dc03890a2b638b3058c44ee26700d7 /src/backend/utils/mmgr/generation.c | |
parent | 632b03da31cbbf4d32193d35031d301bd50d2679 (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/generation.c')
-rw-r--r-- | src/backend/utils/mmgr/generation.c | 81 |
1 files changed, 53 insertions, 28 deletions
diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c index 19390fa5818..10d0fc1f904 100644 --- a/src/backend/utils/mmgr/generation.c +++ b/src/backend/utils/mmgr/generation.c @@ -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 */ |