aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/mmgr/slab.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/slab.c
parent632b03da31cbbf4d32193d35031d301bd50d2679 (diff)
downloadpostgresql-9fa6f00b1308dd10da4eca2f31ccbfc7b35bb461.tar.gz
postgresql-9fa6f00b1308dd10da4eca2f31ccbfc7b35bb461.zip
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/slab.c')
-rw-r--r--src/backend/utils/mmgr/slab.c102
1 files changed, 61 insertions, 41 deletions
diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c
index ee2175278d2..c01c77913a8 100644
--- a/src/backend/utils/mmgr/slab.c
+++ b/src/backend/utils/mmgr/slab.c
@@ -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);