Thread: Rethinking MemoryContext creation
While fooling around with a different problem, I got annoyed at how slow MemoryContext creation and deletion is. A big chunk of that seems to be the palloc/pfree cycle needed to allocate the context header block in TopMemoryContext. After thinking about it for a bit, I decided that it'd be smarter to include the context header in the context's first malloc() allocation, thereby eliminating the palloc/pfree cycle completely. This is not completely cost-free, because it means that we must allocate a context's first block ("keeper" block) immediately, even if no pallocs ever get done in it. However, optimizing for the unused-context case seems like the wrong thing. To do this, we need to move the responsibility for allocating and releasing the context header into the individual context-type-specific modules, which leads to a certain amount of code duplication, but not a lot. A big advantage is that we can simplify the overly-complicated creation API that had the context module calling into mcxt.c which then recursed back to the context module. Further down the pike there might be some additional win, since this gives the context module more control over where to put the header (shared memory maybe?). Another point worth making is that getting the context headers out of TopMemoryContext reduces the cost of transiently having a lot of contexts. Since TopMemoryContext is never reset, and aset.c doesn't give memory back to libc short of a reset, creating a lot of contexts permanently bloats TopMemoryContext even if they all get deleted later. I didn't spend a lot of effort on slab.c or generation.c, just having them do a separate malloc and free for the context header. If anyone's really excited about that, they could be improved later. To cut to the chase: I experimented with simply creating and deleting an aset.c memory context in a tight loop, optionally with one palloc in the context before deleting it. On my machine, the time per loop looked like HEAD with patch no palloc at all 9.2 us 10.3 us palloc 100 bytes 18.5 us 11.6 us palloc 10000 bytes 17.2 us 18.5 us So the unused-context case does get a bit slower, as we trade off a malloc/free cycle to save a palloc/pfree cycle. The case where we've made some use of the context gets considerably quicker though. Also, the last line shows a situation where the lone palloc request does not fit in the context's initial allocation so that we're forced to make two malloc requests not one. This is a bit slower too, but not by much, and I think it's really an unusual case. (As soon as you've made any requests that do fit in the initial allocation, you'd be back to the patch winning.) Overall I'm seeing about a 5% improvement in a "pgbench -S" scenario, although that number is a bit shaky since the run-to-run variation is a few percent anyway. Thoughts, objections? Anyone want to do some other benchmarking? regards, tom lane diff --git a/src/backend/utils/mmgr/README b/src/backend/utils/mmgr/README index 296fa19..7f08f5d 100644 *** a/src/backend/utils/mmgr/README --- b/src/backend/utils/mmgr/README *************** every other context is a direct or indir *** 177,184 **** 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 allocating stuff here unless really necessary, and especially avoid running with CurrentMemoryContext pointing here. --- 177,183 ---- 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. Avoid allocating stuff here unless really necessary, and especially avoid running with CurrentMemoryContext pointing here. *************** a maximum block size. Selecting smaller *** 420,430 **** 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. 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 --- 419,428 ---- 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, in case for ! some reason that should be larger than the initial block size. ! An aset.c context will always contain at least max(initBlockSize, ! minContextSize) space (less some header overhead). 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 diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 1bd1c34..e0ef036 100644 *** a/src/backend/utils/mmgr/aset.c --- b/src/backend/utils/mmgr/aset.c *************** typedef void *AllocPointer; *** 113,119 **** * * 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 * being logically empty, because we don't attempt to detect pfree'ing the * last active chunk. */ --- 113,119 ---- * * 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 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. */ *************** typedef struct AllocSetContext *** 128,134 **** Size maxBlockSize; /* maximum block size */ Size nextBlockSize; /* next block size to allocate */ Size allocChunkLimit; /* effective chunk size limit */ ! AllocBlock keeper; /* if not NULL, keep this block over resets */ } AllocSetContext; typedef AllocSetContext *AllocSet; --- 128,134 ---- Size maxBlockSize; /* maximum block size */ Size nextBlockSize; /* next block size to allocate */ Size allocChunkLimit; /* effective chunk size limit */ ! AllocBlock keeper; /* keep this block over resets */ } AllocSetContext; typedef AllocSetContext *AllocSet; *************** typedef struct AllocChunkData *** 221,227 **** 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); --- 221,226 ---- *************** static void AllocSetCheck(MemoryContext *** 236,246 **** /* * This is the virtual function table for AllocSet contexts. */ ! static MemoryContextMethods AllocSetMethods = { AllocSetAlloc, AllocSetFree, AllocSetRealloc, - AllocSetInit, AllocSetReset, AllocSetDelete, AllocSetGetChunkSpace, --- 235,244 ---- /* * This is the virtual function table for AllocSet contexts. */ ! static const MemoryContextMethods AllocSetMethods = { AllocSetAlloc, AllocSetFree, AllocSetRealloc, AllocSetReset, AllocSetDelete, AllocSetGetChunkSpace, *************** AllocSetContextCreate(MemoryContext pare *** 345,351 **** --- 343,352 ---- Size initBlockSize, Size maxBlockSize) { + Size headerSize; + Size firstBlockSize; AllocSet set; + AllocBlock block; /* Assert we padded AllocChunkData properly */ StaticAssertStmt(ALLOC_CHUNKHDRSZ == MAXALIGN(ALLOC_CHUNKHDRSZ), *************** AllocSetContextCreate(MemoryContext pare *** 370,387 **** 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 */ set->initBlockSize = initBlockSize; set->maxBlockSize = maxBlockSize; set->nextBlockSize = initBlockSize; --- 371,426 ---- maxBlockSize); if (minContextSize != 0 && (minContextSize != MAXALIGN(minContextSize) || ! minContextSize < initBlockSize || ! minContextSize > maxBlockSize)) elog(ERROR, "invalid minContextSize for memory context: %zu", minContextSize); ! /* Size of the memory context header, including name storage */ ! headerSize = MAXALIGN(sizeof(AllocSetContext) + strlen(name) + 1); ! ! /* Determine size of initial block */ ! firstBlockSize = headerSize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; ! firstBlockSize = Max(firstBlockSize, initBlockSize); ! firstBlockSize = Max(firstBlockSize, minContextSize); ! ! /* ! * 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) ! { ! 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 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; ! 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, block->endptr - block->freeptr); ! ! /* 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; *************** AllocSetContextCreate(MemoryContext pare *** 410,483 **** (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); ! } 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. * * 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. */ static void AllocSetReset(MemoryContext context) --- 449,477 ---- (Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION)) set->allocChunkLimit >>= 1; ! /* Finally, do the type-independent part of context creation */ ! MemoryContextCreate((MemoryContext) set, ! T_AllocSetContext, ! headerSize, ! sizeof(AllocSetContext), ! &AllocSetMethods, ! parent, ! name); return (MemoryContext) set; } /* * AllocSetReset * Frees all memory which is allocated in the given set. * * 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 give back all but the "keeper" block (which we must keep, since ! * it also holds 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) *************** AllocSetReset(MemoryContext context) *** 497,503 **** block = set->blocks; ! /* New blocks list is either empty or just the keeper block */ set->blocks = set->keeper; while (block != NULL) --- 491,497 ---- block = set->blocks; ! /* New blocks list will be just the keeper block */ set->blocks = set->keeper; while (block != NULL) *************** AllocSetReset(MemoryContext context) *** 540,546 **** * 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) --- 534,539 ---- *************** AllocSetDelete(MemoryContext context) *** 555,565 **** AllocSetCheck(context); #endif ! /* Make it look empty, just in case... */ ! MemSetAligned(set->freelist, 0, sizeof(set->freelist)); ! set->blocks = NULL; ! set->keeper = NULL; ! while (block != NULL) { AllocBlock next = block->next; --- 548,554 ---- AllocSetCheck(context); #endif ! /* Free all blocks except the keeper */ while (block != NULL) { AllocBlock next = block->next; *************** AllocSetDelete(MemoryContext context) *** 567,575 **** #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif ! free(block); block = next; } } /* --- 556,571 ---- #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif ! ! /* Free the block, unless it's the keeper */ ! if (block != set->keeper) ! free(block); ! block = next; } + + /* Finally, free the context header + keeper block */ + free(set); } /* *************** AllocSetAlloc(MemoryContext context, Siz *** 807,824 **** 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); --- 803,808 ---- *************** AllocSetStats(MemoryContext context, int *** 1205,1215 **** AllocSet set = (AllocSet) context; Size nblocks = 0; Size freechunks = 0; ! Size totalspace = 0; Size freespace = 0; AllocBlock block; int fidx; for (block = set->blocks; block != NULL; block = block->next) { nblocks++; --- 1189,1202 ---- AllocSet set = (AllocSet) context; Size nblocks = 0; Size freechunks = 0; ! Size totalspace; Size freespace = 0; AllocBlock block; int fidx; + /* Include context header in totalspace */ + totalspace = MAXALIGN(sizeof(AllocSetContext) + strlen(context->name) + 1); + for (block = set->blocks; block != NULL; block = block->next) { nblocks++; diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c index 19390fa..ed9560a 100644 *** a/src/backend/utils/mmgr/generation.c --- b/src/backend/utils/mmgr/generation.c *************** struct GenerationChunk *** 149,155 **** 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); --- 149,154 ---- *************** static void GenerationCheck(MemoryContex *** 164,174 **** /* * This is the virtual function table for Generation contexts. */ ! static MemoryContextMethods GenerationMethods = { GenerationAlloc, GenerationFree, GenerationRealloc, - GenerationInit, GenerationReset, GenerationDelete, GenerationGetChunkSpace, --- 163,172 ---- /* * This is the virtual function table for Generation contexts. */ ! static const MemoryContextMethods GenerationMethods = { GenerationAlloc, GenerationFree, GenerationRealloc, GenerationReset, GenerationDelete, GenerationGetChunkSpace, *************** GenerationContextCreate(MemoryContext pa *** 210,215 **** --- 208,214 ---- const char *name, Size blockSize) { + Size headerSize; GenerationContext *set; /* Assert we padded GenerationChunk properly */ *************** GenerationContextCreate(MemoryContext pa *** 231,259 **** 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); ! set->blockSize = blockSize; ! return (MemoryContext) set; ! } ! /* ! * GenerationInit ! * Context-type-specific initialization routine. ! */ ! static void ! GenerationInit(MemoryContext context) ! { ! GenerationContext *set = (GenerationContext *) context; set->block = NULL; dlist_init(&set->blocks); } /* --- 230,275 ---- elog(ERROR, "invalid blockSize for memory context: %zu", blockSize); ! /* ! * Allocate the context header. Unlike aset.c, we don't try to put this ! * into the first regular block, since that would prevent us from freeing ! * the first generation of allocations. ! */ ! /* Size of the memory context header, including name storage */ ! headerSize = sizeof(GenerationContext) + strlen(name) + 1; ! 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))); ! } ! /* ! * 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->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); + + return (MemoryContext) set; } /* *************** GenerationReset(MemoryContext context) *** 296,311 **** /* * 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. */ static void GenerationDelete(MemoryContext context) { ! /* just reset to release all the GenerationBlocks */ GenerationReset(context); ! /* we are not responsible for deleting the context node itself */ } /* --- 312,326 ---- /* * GenerationDelete ! * Free all memory which is allocated in the given context. */ static void GenerationDelete(MemoryContext context) { ! /* Reset to release all the GenerationBlocks */ GenerationReset(context); ! /* And free the context header */ ! free(context); } /* *************** GenerationIsEmpty(MemoryContext context) *** 659,665 **** /* * GenerationStats ! * Compute stats about memory consumption of an Generation. * * level: recursion level (0 at top level); used for print indentation. * print: true to print stats to stderr. --- 674,680 ---- /* * GenerationStats ! * 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. *************** GenerationStats(MemoryContext context, i *** 676,685 **** Size nblocks = 0; Size nchunks = 0; Size nfreechunks = 0; ! Size totalspace = 0; Size freespace = 0; dlist_iter iter; dlist_foreach(iter, &set->blocks) { GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); --- 691,703 ---- Size nblocks = 0; Size nchunks = 0; Size nfreechunks = 0; ! Size totalspace; Size freespace = 0; dlist_iter iter; + /* Include context header in totalspace */ + totalspace = sizeof(GenerationContext) + strlen(context->name) + 1; + dlist_foreach(iter, &set->blocks) { GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index c5c311f..d56e810 100644 *** a/src/backend/utils/mmgr/mcxt.c --- b/src/backend/utils/mmgr/mcxt.c *************** MemoryContextInit(void) *** 91,99 **** 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.) */ TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", --- 91,97 ---- AssertState(TopMemoryContext == NULL); /* ! * First, initialize TopMemoryContext, which is the parent of all others. */ TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", *************** MemoryContextResetChildren(MemoryContext *** 191,200 **** * 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. */ void MemoryContextDelete(MemoryContext context) --- 189,197 ---- * Delete a context and its descendants, and release all space * allocated therein. * ! * 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) *************** MemoryContextDelete(MemoryContext contex *** 223,230 **** MemoryContextSetParent(context, NULL); context->methods->delete_context(context); VALGRIND_DESTROY_MEMPOOL(context); - pfree(context); } /* --- 220,227 ---- MemoryContextSetParent(context, NULL); context->methods->delete_context(context); + VALGRIND_DESTROY_MEMPOOL(context); } /* *************** MemoryContextContains(MemoryContext cont *** 587,686 **** 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 * 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. * ! * Note that the name field of a MemoryContext does not point to ! * separately-allocated storage, so it should not be freed at context ! * deletion. ! *-------------------- */ ! MemoryContext ! MemoryContextCreate(NodeTag tag, Size size, ! MemoryContextMethods *methods, MemoryContext parent, const char *name) { ! MemoryContext node; ! Size needed = size + strlen(name) + 1; ! ! /* 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); ! } ! /* Initialize the node as best we can */ ! MemSet(node, 0, size); node->type = tag; node->methods = methods; ! node->parent = NULL; /* for the moment */ node->firstchild = NULL; node->prevchild = NULL; ! node->nextchild = NULL; ! node->isReset = true; ! node->name = ((char *) node) + size; ! strcpy(node->name, name); ! /* Type-specific routine finishes any other essential initialization */ ! node->methods->init(node); ! /* OK to link node to parent (if any) */ ! /* Could use MemoryContextSetParent here, but doesn't seem worthwhile */ if (parent) { - node->parent = parent; node->nextchild = parent->firstchild; if (parent->firstchild != NULL) parent->firstchild->prevchild = node; --- 584,654 ---- 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 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. * ! * 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. ! * 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). * ! * Context routines generally assume that MemoryContextCreate can't fail, ! * so this can contain Assert but not elog/ereport. */ ! void ! MemoryContextCreate(MemoryContext node, ! NodeTag tag, Size size, Size nameoffset, ! const MemoryContextMethods *methods, MemoryContext parent, const char *name) { ! /* Creating new memory contexts is not allowed in a critical section */ Assert(CritSectionCount == 0); ! /* Check size is sane */ ! Assert(nameoffset >= sizeof(MemoryContextData)); ! Assert(size >= nameoffset + strlen(name) + 1); ! /* Initialize all standard fields of memory context header */ node->type = tag; + node->isReset = true; node->methods = methods; ! node->parent = parent; node->firstchild = NULL; node->prevchild = NULL; ! node->reset_cbs = NULL; ! /* Insert context name into space reserved for it */ ! node->name = ((char *) node) + nameoffset; ! strcpy(node->name, name); ! /* OK to link node into context tree */ if (parent) { node->nextchild = parent->firstchild; if (parent->firstchild != NULL) parent->firstchild->prevchild = node; *************** MemoryContextCreate(NodeTag tag, Size si *** 688,698 **** /* inherit allowInCritSection flag from parent */ node->allowInCritSection = parent->allowInCritSection; } VALGRIND_CREATE_MEMPOOL(node, 0, false); - - /* Return to type-specific creation routine to finish up */ - return node; } /* --- 656,668 ---- /* inherit allowInCritSection flag from parent */ node->allowInCritSection = parent->allowInCritSection; } + else + { + node->nextchild = NULL; + node->allowInCritSection = false; + } VALGRIND_CREATE_MEMPOOL(node, 0, false); } /* diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c index ee21752..3532e83 100644 *** a/src/backend/utils/mmgr/slab.c --- b/src/backend/utils/mmgr/slab.c *************** typedef struct SlabChunk *** 126,132 **** 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); --- 126,131 ---- *************** static void SlabCheck(MemoryContext cont *** 140,150 **** /* * This is the virtual function table for Slab contexts. */ ! static MemoryContextMethods SlabMethods = { SlabAlloc, SlabFree, SlabRealloc, - SlabInit, SlabReset, SlabDelete, SlabGetChunkSpace, --- 139,148 ---- /* * This is the virtual function table for Slab contexts. */ ! static const MemoryContextMethods SlabMethods = { SlabAlloc, SlabFree, SlabRealloc, SlabReset, SlabDelete, SlabGetChunkSpace, *************** SlabContextCreate(MemoryContext parent, *** 194,200 **** --- 192,201 ---- 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)), *************** SlabContextCreate(MemoryContext parent, *** 227,265 **** /* make sure the chunks actually fit on the block */ Assert((fullChunkSize * chunksPerBlock) + sizeof(SlabBlock) <= blockSize); ! /* Do the type-independent part of context creation */ ! slab = (SlabContext *) ! MemoryContextCreate(T_SlabContext, ! (offsetof(SlabContext, freelist) + freelistSize), ! &SlabMethods, ! parent, ! name); ! slab->blockSize = blockSize; slab->chunkSize = chunkSize; slab->fullChunkSize = fullChunkSize; 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); /* initialize the freelist slots */ for (i = 0; i < (slab->chunksPerBlock + 1); i++) dlist_init(&slab->freelist[i]); } /* --- 228,280 ---- /* make sure the chunks actually fit on the block */ Assert((fullChunkSize * chunksPerBlock) + sizeof(SlabBlock) <= blockSize); ! /* ! * Allocate the context header. Unlike aset.c, we don't try to put this ! * into the first regular block; not worth the extra complication. ! */ ! /* Size of the memory context header, including name storage */ ! nameOffset = offsetof(SlabContext, freelist) + freelistSize; ! headerSize = nameOffset + strlen(name) + 1; ! ! 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))); ! } ! ! /* ! * 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->chunksPerBlock = chunksPerBlock; slab->minFreeChunks = 0; ! 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); + + return (MemoryContext) slab; } /* *************** SlabReset(MemoryContext context) *** 308,321 **** /* * SlabDelete ! * Frees all memory which is allocated in the given slab, in preparation ! * for deletion of the slab. We simply call SlabReset(). */ static void SlabDelete(MemoryContext context) { ! /* just reset the context */ SlabReset(context); } /* --- 323,337 ---- /* * SlabDelete ! * Free all memory which is allocated in the given context. */ static void SlabDelete(MemoryContext context) { ! /* Reset to release all the SlabBlocks */ SlabReset(context); + /* And free the context header */ + free(context); } /* *************** SlabIsEmpty(MemoryContext context) *** 613,619 **** /* * SlabStats ! * Compute stats about memory consumption of an Slab. * * level: recursion level (0 at top level); used for print indentation. * print: true to print stats to stderr. --- 629,635 ---- /* * SlabStats ! * 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. *************** SlabStats(MemoryContext context, int lev *** 626,636 **** SlabContext *slab = castNode(SlabContext, context); Size nblocks = 0; Size freechunks = 0; ! Size totalspace = 0; Size freespace = 0; int i; ! Assert(slab); for (i = 0; i <= slab->chunksPerBlock; i++) { --- 642,655 ---- SlabContext *slab = castNode(SlabContext, context); Size nblocks = 0; Size freechunks = 0; ! Size totalspace; Size freespace = 0; int i; ! /* Include context header in totalspace */ ! totalspace = offsetof(SlabContext, freelist) ! + sizeof(dlist_head) * (slab->chunksPerBlock + 1) ! + strlen(context->name) + 1; for (i = 0; i <= slab->chunksPerBlock; i++) { diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h index e22d9fb..f49d55e 100644 *** a/src/include/nodes/memnodes.h --- b/src/include/nodes/memnodes.h *************** typedef struct MemoryContextMethods *** 57,63 **** /* 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); --- 57,62 ---- *************** typedef struct MemoryContextData *** 76,82 **** /* 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 */ MemoryContext parent; /* NULL if no parent (toplevel context) */ MemoryContext firstchild; /* head of linked list of children */ MemoryContext prevchild; /* previous child of same parent */ --- 75,81 ---- /* 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 */ ! 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 */ diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index d177b0c..4ac759e 100644 *** a/src/include/utils/memutils.h --- b/src/include/utils/memutils.h *************** GetMemoryChunkContext(void *pointer) *** 132,139 **** * 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, MemoryContext parent, const char *name); --- 132,140 ---- * context creation. It's intended to be called from context-type- * specific creation routines, and noplace else. */ ! extern void MemoryContextCreate(MemoryContext node, ! NodeTag tag, Size size, Size nameoffset, ! const MemoryContextMethods *methods, MemoryContext parent, const char *name);
On Sat, Dec 9, 2017 at 5:53 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > Overall I'm seeing about a 5% improvement in a "pgbench -S" scenario, > although that number is a bit shaky since the run-to-run variation > is a few percent anyway. Is that with "-M prepared", too? -- Peter Geoghegan
Peter Geoghegan <pg@bowt.ie> writes: > On Sat, Dec 9, 2017 at 5:53 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Overall I'm seeing about a 5% improvement in a "pgbench -S" scenario, >> although that number is a bit shaky since the run-to-run variation >> is a few percent anyway. > Is that with "-M prepared", too? No, I didn't use that. regards, tom lane
I wrote: > While fooling around with a different problem, I got annoyed at how slow > MemoryContext creation and deletion is. Looking at this some more, there's another micro-optimization we could make, which is to try to get rid of the strlen+strcpy operations needed for the context name. (And yes, I'm seeing those show up in profiles, to the tune of a couple percent of total runtime in some examples.) For a *very* large majority of the callers of AllocSetContextCreate, the context name is a simple C string constant, so we could just store the pointer to it and save the space and cycles required to copy it. This would require providing a separate API for the few callers that are actually passing transient strings, but that's easy enough. I envision AllocSetContextCreate becoming a wrapper macro for "AllocSetContextCreateExtended", which'd take a flag argument specifying whether the context name needs to be copied. However, unless we want to run around and touch all the ~ 150 calls with constant arguments, we'd have to set things up so that the default behavior for AllocSetContextCreate is to not copy. This risks breaking callers in extensions. Not entirely sure if it's worth that --- any thoughts? regards, tom lane
On 12/11/2017 05:27 PM, Tom Lane wrote: > I wrote: >> While fooling around with a different problem, I got annoyed at how slow >> MemoryContext creation and deletion is. > > Looking at this some more, there's another micro-optimization we could > make, which is to try to get rid of the strlen+strcpy operations needed > for the context name. (And yes, I'm seeing those show up in profiles, > to the tune of a couple percent of total runtime in some examples.) > > For a *very* large majority of the callers of AllocSetContextCreate, > the context name is a simple C string constant, so we could just store > the pointer to it and save the space and cycles required to copy it. > This would require providing a separate API for the few callers that are > actually passing transient strings, but that's easy enough. I envision > AllocSetContextCreate becoming a wrapper macro for > "AllocSetContextCreateExtended", which'd take a flag argument specifying > whether the context name needs to be copied. > > However, unless we want to run around and touch all the ~ 150 calls > with constant arguments, we'd have to set things up so that the default > behavior for AllocSetContextCreate is to not copy. This risks breaking > callers in extensions. Not entirely sure if it's worth that --- any > thoughts? > I don't think silently breaking extensions is particularly attractive option, so I guess we'll have to run around and tweak the ~150 calls. regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes: > On 12/11/2017 05:27 PM, Tom Lane wrote: >> However, unless we want to run around and touch all the ~ 150 calls >> with constant arguments, we'd have to set things up so that the default >> behavior for AllocSetContextCreate is to not copy. This risks breaking >> callers in extensions. Not entirely sure if it's worth that --- any >> thoughts? > I don't think silently breaking extensions is particularly attractive > option, so I guess we'll have to run around and tweak the ~150 calls. Meh. I suppose that of the ~150 call sites, there are probably only a dozen or two where it would actually make a performance difference, so maybe this needn't be quite as invasive as I first thought. regards, tom lane
On Mon, Dec 11, 2017 at 11:59 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > Tomas Vondra <tomas.vondra@2ndquadrant.com> writes: >> On 12/11/2017 05:27 PM, Tom Lane wrote: >>> However, unless we want to run around and touch all the ~ 150 calls >>> with constant arguments, we'd have to set things up so that the default >>> behavior for AllocSetContextCreate is to not copy. This risks breaking >>> callers in extensions. Not entirely sure if it's worth that --- any >>> thoughts? > >> I don't think silently breaking extensions is particularly attractive >> option, so I guess we'll have to run around and tweak the ~150 calls. > > Meh. I suppose that of the ~150 call sites, there are probably only > a dozen or two where it would actually make a performance difference, > so maybe this needn't be quite as invasive as I first thought. I think changing only a subset of the call sites is unappealing because, even though it may not make a measurable performance difference in other cases, it may get cargo-culted into some place where it does make a difference. I also don't think silently breaking extensions is a terribly big deal in this case. It seems likely that most extensions use static names just as most of our internal stuff does. I'm going to guess that the number of extensions that will actually break as a result of a change in this area is probably very small - conceivably zero, and likely less than five. I don't think we should be willing to uglify the core code too much for that level of breakage. But those are just my opinions. I am glad you are working on this; I noticed this problem before and thought of trying to do something about it, but ran out of time and ideas. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
On 11 December 2017 at 16:27, Tom Lane <tgl@sss.pgh.pa.us> wrote: > For a *very* large majority of the callers of AllocSetContextCreate, > the context name is a simple C string constant, so we could just store > the pointer to it and save the space and cycles required to copy it. Why have the string at all in that case? -- Simon Riggs http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Robert Haas <robertmhaas@gmail.com> writes: > On Mon, Dec 11, 2017 at 11:59 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Tomas Vondra <tomas.vondra@2ndquadrant.com> writes: >>> On 12/11/2017 05:27 PM, Tom Lane wrote: >>>> However, unless we want to run around and touch all the ~ 150 calls >>>> with constant arguments, we'd have to set things up so that the default >>>> behavior for AllocSetContextCreate is to not copy. This risks breaking >>>> callers in extensions. Not entirely sure if it's worth that --- any >>>> thoughts? >>> I don't think silently breaking extensions is particularly attractive >>> option, so I guess we'll have to run around and tweak the ~150 calls. > I also don't think silently breaking extensions is a terribly big deal > in this case. It seems likely that most extensions use static names > just as most of our internal stuff does. I'm going to guess that the > number of extensions that will actually break as a result of a change > in this area is probably very small - conceivably zero, and likely > less than five. I don't think we should be willing to uglify the core > code too much for that level of breakage. [ thinks... ] If we wanted to go that way, one thing we could do to help extension authors (and ourselves) is to define the proposed AllocSetContextCreate macro to include StaticAssertExpr(__builtin_constant_p(name)) on compilers that have __builtin_constant_p. Now, that only helps people using gcc and gcc-alikes, but that's a large fraction of developers I should think. (I tested this and it does seem to correctly recognize string literals as constants.) regards, tom lane
On 12/11/2017 06:22 PM, Robert Haas wrote: > On Mon, Dec 11, 2017 at 11:59 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Tomas Vondra <tomas.vondra@2ndquadrant.com> writes: >>> On 12/11/2017 05:27 PM, Tom Lane wrote: >>>> However, unless we want to run around and touch all the ~ 150 calls >>>> with constant arguments, we'd have to set things up so that the default >>>> behavior for AllocSetContextCreate is to not copy. This risks breaking >>>> callers in extensions. Not entirely sure if it's worth that --- any >>>> thoughts? >> >>> I don't think silently breaking extensions is particularly attractive >>> option, so I guess we'll have to run around and tweak the ~150 calls. >> >> Meh. I suppose that of the ~150 call sites, there are probably only >> a dozen or two where it would actually make a performance difference, >> so maybe this needn't be quite as invasive as I first thought. > > I think changing only a subset of the call sites is unappealing > because, even though it may not make a measurable performance > difference in other cases, it may get cargo-culted into some place > where it does make a difference. > Not sure. One the one hand, it might certainly be somewhat confusing when we use two different methods to create memory contexts with C string constants. On the other hand, I'm sure we have other similar "local" optimizations. I'd say "let's just tweak all the calls to use the new function" but I'm not the person doing the leg work ... > I also don't think silently breaking extensions is a terribly big deal > in this case. It seems likely that most extensions use static names > just as most of our internal stuff does. I'm going to guess that the > number of extensions that will actually break as a result of a change > in this area is probably very small - conceivably zero, and likely > less than five. I don't think we should be willing to uglify the core > code too much for that level of breakage. > I don't know how many extensions you maintain, but in my experience this type of silent breakage is extremely annoying. I definitely prefer a clean compile-time API breakage, for example. regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Simon Riggs <simon@2ndquadrant.com> writes: > On 11 December 2017 at 16:27, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> For a *very* large majority of the callers of AllocSetContextCreate, >> the context name is a simple C string constant, so we could just store >> the pointer to it and save the space and cycles required to copy it. > Why have the string at all in that case? Try reading a MemoryContextStats dump without it ... regards, tom lane
Robert Haas <robertmhaas@gmail.com> writes: > On Mon, Dec 11, 2017 at 11:59 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Tomas Vondra <tomas.vondra@2ndquadrant.com> writes: >>> On 12/11/2017 05:27 PM, Tom Lane wrote: >>>> However, unless we want to run around and touch all the ~ 150 calls >>>> with constant arguments, we'd have to set things up so that the default >>>> behavior for AllocSetContextCreate is to not copy. This risks breaking >>>> callers in extensions. Not entirely sure if it's worth that --- any >>>> thoughts? >> >>> I don't think silently breaking extensions is particularly attractive >>> option, so I guess we'll have to run around and tweak the ~150 calls. >> >> Meh. I suppose that of the ~150 call sites, there are probably only >> a dozen or two where it would actually make a performance difference, >> so maybe this needn't be quite as invasive as I first thought. > > I think changing only a subset of the call sites is unappealing > because, even though it may not make a measurable performance > difference in other cases, it may get cargo-culted into some place > where it does make a difference. Would it be acceptable to only get this optimisation on compilers that support __builtin_constant_p or similar? If so, the wrapper macro could use that to automatically pass the no-copy flag when called with a literal string. - ilmari -- "I use RMS as a guide in the same way that a boat captain would use a lighthouse. It's good to know where it is, but you generally don't want to find yourself in the same spot." - Tollef Fog Heen
On Mon, Dec 11, 2017 at 12:36 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > [ thinks... ] If we wanted to go that way, one thing we could do to > help extension authors (and ourselves) is to define the proposed > AllocSetContextCreate macro to include > > StaticAssertExpr(__builtin_constant_p(name)) > > on compilers that have __builtin_constant_p. Now, that only helps > people using gcc and gcc-alikes, but that's a large fraction of > developers I should think. (I tested this and it does seem to > correctly recognize string literals as constants.) I like that idea. I think that would provide good protection not only for third-party developers but for core developers. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
On 12/10/2017 04:42 PM, Tom Lane wrote: > Peter Geoghegan <pg@bowt.ie> writes: >> On Sat, Dec 9, 2017 at 5:53 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >>> Overall I'm seeing about a 5% improvement in a "pgbench -S" scenario, >>> although that number is a bit shaky since the run-to-run variation >>> is a few percent anyway. > >> Is that with "-M prepared", too? > > No, I didn't use that. > FWIW I've done some measurements, and while there is a improvement, it's far from 5%. pgbench -S -c 1 -T 60 master patched ----------------- 18244 18534 18369 18587 18310 18479 18346 18515 18344 18557 pgbench -S -M prepared -c 1 -T 60 master patched ----------------- 35191 35231 35115 35555 35164 35686 35110 35724 35053 35762 So that's about 1.3% and 1.2% improvement. It seems fairly consistent, but it might easily be due to different in layout of the binaries. regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes: > On 12/10/2017 04:42 PM, Tom Lane wrote: >> Peter Geoghegan <pg@bowt.ie> writes: >>> On Sat, Dec 9, 2017 at 5:53 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >>>> Overall I'm seeing about a 5% improvement in a "pgbench -S" scenario, >>>> although that number is a bit shaky since the run-to-run variation >>>> is a few percent anyway. > FWIW I've done some measurements, and while there is a improvement, it's > far from 5%. ... > So that's about 1.3% and 1.2% improvement. It seems fairly consistent, > but it might easily be due to different in layout of the binaries. Thanks for checking. With these sorts of small-percentage improvements, I would not be surprised for platform-to-platform results to be different. At least you do see some improvement too. Let me code up the change to avoid copying constant name strings, and then we can see if that helps any. regards, tom lane
On 2017-12-11 13:09:42 -0500, Tom Lane wrote: > Tomas Vondra <tomas.vondra@2ndquadrant.com> writes: > > On 12/10/2017 04:42 PM, Tom Lane wrote: > >> Peter Geoghegan <pg@bowt.ie> writes: > >>> On Sat, Dec 9, 2017 at 5:53 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > >>>> Overall I'm seeing about a 5% improvement in a "pgbench -S" scenario, > >>>> although that number is a bit shaky since the run-to-run variation > >>>> is a few percent anyway. > > > FWIW I've done some measurements, and while there is a improvement, it's > > far from 5%. ... > > So that's about 1.3% and 1.2% improvement. It seems fairly consistent, > > but it might easily be due to different in layout of the binaries. > > Thanks for checking. With these sorts of small-percentage improvements, > I would not be surprised for platform-to-platform results to be different. > At least you do see some improvement too. > > Let me code up the change to avoid copying constant name strings, > and then we can see if that helps any. FWIW: + 5.37% postgres postgres [.] hash_search_with_hash_value + 2.94% postgres postgres [.] AllocSetAlloc + 2.68% postgres postgres [.] _bt_compare + 2.36% postgres postgres [.] LWLockAcquire + 2.13% postgres postgres [.] PostgresMain + 1.47% postgres postgres [.] LWLockRelease + 1.32% postgres libc-2.25.so [.] _int_malloc + 1.23% postgres libc-2.25.so [.] vfprintf + 1.22% postgres postgres [.] LockAcquire + 1.20% postgres postgres [.] _bt_first - 1.11% postgres libc-2.25.so [.] strlen - strlen + 32.24% MemoryContextCreate + 16.97% pq_getmsgstring + 14.63% set_ps_display So I'd expect it to help a small amount. - Andres
Robert Haas <robertmhaas@gmail.com> writes: > On Mon, Dec 11, 2017 at 12:36 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> [ thinks... ] If we wanted to go that way, one thing we could do to >> help extension authors (and ourselves) is to define the proposed >> AllocSetContextCreate macro to include >> >> StaticAssertExpr(__builtin_constant_p(name)) >> >> on compilers that have __builtin_constant_p. Now, that only helps >> people using gcc and gcc-alikes, but that's a large fraction of >> developers I should think. (I tested this and it does seem to >> correctly recognize string literals as constants.) > I like that idea. I think that would provide good protection not only > for third-party developers but for core developers. It turns out this is slightly more painful than I'd anticipated. I tried to #define AllocSetContextCreate with five parameters, but all of the call sites that use the size abstraction macros (ALLOCSET_DEFAULT_SIZES and friends) blew up, because as far as they were concerned there were only three parameters, since the abstraction macros hadn't gotten expanded yet. We can make it work by #defining AllocSetContextCreate with three parameters #define AllocSetContextCreate(parent, name, allocparams) ... This approach means that you *must* use an abstraction macro when going through AllocSetContextCreate; if you want to write out the parameters longhand, you have to call AllocSetContextCreateExtended. I do not feel that this is a big loss, but there were half a dozen sites in our code that were doing it the old way. More significantly, since we only introduced those macros in 9.6, I suspect that most extensions are still doing it the old way and will get broken by this change. It's not hard to fix, but the annoyance factor will probably be real. I see no good way around it though: we can't use a static inline function instead, because that will almost certainly break the __builtin_constant_p test. I did not bother with compatibility macros for SlabContext or GenerationContext --- I really doubt any extension code is using the former yet, and they couldn't be using the latter since it's new. I've not done any benchmarking on this yet, just confirmed that it compiles and passes check-world. regards, tom lane diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c index 868c14e..adbbc44 100644 *** a/contrib/amcheck/verify_nbtree.c --- b/contrib/amcheck/verify_nbtree.c *************** bt_check_every_level(Relation rel, bool *** 295,303 **** /* Create context for page */ state->targetcontext = AllocSetContextCreate(CurrentMemoryContext, "amcheck context", ! ALLOCSET_DEFAULT_MINSIZE, ! ALLOCSET_DEFAULT_INITSIZE, ! ALLOCSET_DEFAULT_MAXSIZE); state->checkstrategy = GetAccessStrategy(BAS_BULKREAD); /* Get true root block from meta-page */ --- 295,301 ---- /* Create context for page */ state->targetcontext = AllocSetContextCreate(CurrentMemoryContext, "amcheck context", ! ALLOCSET_DEFAULT_SIZES); state->checkstrategy = GetAccessStrategy(BAS_BULKREAD); /* Get true root block from meta-page */ diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 046898c..e93d740 100644 *** a/src/backend/access/transam/xact.c --- b/src/backend/access/transam/xact.c *************** AtStart_Memory(void) *** 997,1007 **** */ if (TransactionAbortContext == NULL) TransactionAbortContext = ! AllocSetContextCreate(TopMemoryContext, ! "TransactionAbortContext", ! 32 * 1024, ! 32 * 1024, ! 32 * 1024); /* * We shouldn't have a transaction context already. --- 997,1008 ---- */ if (TransactionAbortContext == NULL) TransactionAbortContext = ! AllocSetContextCreateExtended(TopMemoryContext, ! "TransactionAbortContext", ! 0, ! 32 * 1024, ! 32 * 1024, ! 32 * 1024); /* * We shouldn't have a transaction context already. diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index dd4a8d3..6e27856 100644 *** a/src/backend/catalog/partition.c --- b/src/backend/catalog/partition.c *************** RelationBuildPartitionDesc(Relation rel) *** 513,521 **** } /* Now build the actual relcache partition descriptor */ ! rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(rel), ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt); result = (PartitionDescData *) palloc0(sizeof(PartitionDescData)); --- 513,522 ---- } /* Now build the actual relcache partition descriptor */ ! rel->rd_pdcxt = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(rel), ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt); result = (PartitionDescData *) palloc0(sizeof(PartitionDescData)); diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 086a6ef..a7f426d 100644 *** a/src/backend/commands/subscriptioncmds.c --- b/src/backend/commands/subscriptioncmds.c *************** publicationListToArray(List *publist) *** 259,267 **** /* Create memory context for temporary allocations. */ memcxt = AllocSetContextCreate(CurrentMemoryContext, "publicationListToArray to array", ! ALLOCSET_DEFAULT_MINSIZE, ! ALLOCSET_DEFAULT_INITSIZE, ! ALLOCSET_DEFAULT_MAXSIZE); oldcxt = MemoryContextSwitchTo(memcxt); datums = (Datum *) palloc(sizeof(Datum) * list_length(publist)); --- 259,265 ---- /* Create memory context for temporary allocations. */ memcxt = AllocSetContextCreate(CurrentMemoryContext, "publicationListToArray to array", ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(memcxt); datums = (Datum *) palloc(sizeof(Datum) * list_length(publist)); diff --git a/src/backend/lib/knapsack.c b/src/backend/lib/knapsack.c index ddf2b9a..490c0cc 100644 *** a/src/backend/lib/knapsack.c --- b/src/backend/lib/knapsack.c *************** DiscreteKnapsack(int max_weight, int num *** 57,65 **** { MemoryContext local_ctx = AllocSetContextCreate(CurrentMemoryContext, "Knapsack", ! ALLOCSET_SMALL_MINSIZE, ! ALLOCSET_SMALL_INITSIZE, ! ALLOCSET_SMALL_MAXSIZE); MemoryContext oldctx = MemoryContextSwitchTo(local_ctx); double *values; Bitmapset **sets; --- 57,63 ---- { MemoryContext local_ctx = AllocSetContextCreate(CurrentMemoryContext, "Knapsack", ! ALLOCSET_SMALL_SIZES); MemoryContext oldctx = MemoryContextSwitchTo(local_ctx); double *values; Bitmapset **sets; diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c index a613ef4..24be3ce 100644 *** a/src/backend/replication/logical/launcher.c --- b/src/backend/replication/logical/launcher.c *************** ApplyLauncherMain(Datum main_arg) *** 925,933 **** /* 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); oldctx = MemoryContextSwitchTo(subctx); /* search for subscriptions to start or stop. */ --- 925,931 ---- /* Use temporary context for the database list and worker info. */ subctx = AllocSetContextCreate(TopMemoryContext, "Logical Replication Launcher sublist", ! ALLOCSET_DEFAULT_SIZES); oldctx = MemoryContextSwitchTo(subctx); /* search for subscriptions to start or stop. */ diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index fa95bab..5ac391d 100644 *** a/src/backend/replication/logical/reorderbuffer.c --- b/src/backend/replication/logical/reorderbuffer.c *************** ReorderBufferAllocate(void) *** 237,252 **** --- 237,255 ---- 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); diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index c312654..550b156 100644 *** a/src/backend/replication/pgoutput/pgoutput.c --- b/src/backend/replication/pgoutput/pgoutput.c *************** pgoutput_startup(LogicalDecodingContext *** 152,160 **** /* 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); ctx->output_plugin_private = data; --- 152,158 ---- /* Create our memory context for private allocations. */ data->context = AllocSetContextCreate(ctx->context, "logical replication output context", ! ALLOCSET_DEFAULT_SIZES); ctx->output_plugin_private = data; diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 12a5f15..ca51a92 100644 *** a/src/backend/utils/cache/relcache.c --- b/src/backend/utils/cache/relcache.c *************** RelationBuildRuleLock(Relation relation) *** 669,677 **** /* * Make the private context. Assume it'll not contain much data. */ ! rulescxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(relation), ! ALLOCSET_SMALL_SIZES); relation->rd_rulescxt = rulescxt; /* --- 669,678 ---- /* * Make the private context. Assume it'll not contain much data. */ ! rulescxt = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(relation), ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_SMALL_SIZES); relation->rd_rulescxt = rulescxt; /* *************** RelationBuildPartitionKey(Relation relat *** 984,992 **** ReleaseSysCache(tuple); /* Success --- now copy to the cache memory */ ! partkeycxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(relation), ! ALLOCSET_SMALL_SIZES); relation->rd_partkeycxt = partkeycxt; oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt); relation->rd_partkey = copy_partition_key(key); --- 985,994 ---- ReleaseSysCache(tuple); /* Success --- now copy to the cache memory */ ! partkeycxt = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(relation), ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_SMALL_SIZES); relation->rd_partkeycxt = partkeycxt; oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt); relation->rd_partkey = copy_partition_key(key); *************** RelationInitIndexAccessInfo(Relation rel *** 1566,1574 **** * 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); relation->rd_indexcxt = indexcxt; /* --- 1568,1577 ---- * 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 = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(relation), ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_SMALL_SIZES); relation->rd_indexcxt = indexcxt; /* *************** load_relcache_init_file(bool shared) *** 5537,5545 **** * prepare index info context --- parameters should match * RelationInitIndexAccessInfo */ ! indexcxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(rel), ! ALLOCSET_SMALL_SIZES); rel->rd_indexcxt = indexcxt; /* --- 5540,5550 ---- * prepare index info context --- parameters should match * RelationInitIndexAccessInfo */ ! indexcxt = ! AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(rel), ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_SMALL_SIZES); rel->rd_indexcxt = indexcxt; /* diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c index da5c8ea..3139b92 100644 *** a/src/backend/utils/cache/ts_cache.c --- b/src/backend/utils/cache/ts_cache.c *************** lookup_ts_dictionary_cache(Oid dictId) *** 294,302 **** 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); } else { --- 294,303 ---- Assert(!found); /* it wasn't there a moment ago */ /* Create private memory context the first time through */ ! saveCtx = AllocSetContextCreateExtended(CacheMemoryContext, ! NameStr(dict->dictname), ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_SMALL_SIZES); } else { diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index 71f5f06..b209433 100644 *** a/src/backend/utils/hash/dynahash.c --- b/src/backend/utils/hash/dynahash.c *************** hash_create(const char *tabname, long ne *** 340,348 **** CurrentDynaHashCxt = info->hcxt; else CurrentDynaHashCxt = TopMemoryContext; ! CurrentDynaHashCxt = AllocSetContextCreate(CurrentDynaHashCxt, ! tabname, ! ALLOCSET_DEFAULT_SIZES); } /* Initialize the hash header, plus a copy of the table name */ --- 340,350 ---- CurrentDynaHashCxt = info->hcxt; else CurrentDynaHashCxt = TopMemoryContext; ! CurrentDynaHashCxt = ! AllocSetContextCreateExtended(CurrentDynaHashCxt, ! tabname, ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_DEFAULT_SIZES); } /* Initialize the hash header, plus a copy of the table name */ diff --git a/src/backend/utils/mmgr/README b/src/backend/utils/mmgr/README index 296fa19..a42e568 100644 *** a/src/backend/utils/mmgr/README --- b/src/backend/utils/mmgr/README *************** every other context is a direct or indir *** 177,184 **** 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 allocating stuff here unless really necessary, and especially avoid running with CurrentMemoryContext pointing here. --- 177,183 ---- 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. Avoid allocating stuff here unless really necessary, and especially avoid running with CurrentMemoryContext pointing here. *************** a maximum block size. Selecting smaller *** 420,430 **** 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. 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 --- 419,428 ---- 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, 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 diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 1bd1c34..61bd3aa 100644 *** a/src/backend/utils/mmgr/aset.c --- b/src/backend/utils/mmgr/aset.c *************** typedef void *AllocPointer; *** 113,119 **** * * 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 * being logically empty, because we don't attempt to detect pfree'ing the * last active chunk. */ --- 113,119 ---- * * 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 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. */ *************** typedef struct AllocSetContext *** 127,134 **** Size initBlockSize; /* initial block size */ Size maxBlockSize; /* maximum block size */ Size nextBlockSize; /* next block size to allocate */ Size allocChunkLimit; /* effective chunk size limit */ ! AllocBlock keeper; /* if not NULL, keep this block over resets */ } AllocSetContext; typedef AllocSetContext *AllocSet; --- 127,135 ---- 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; /* keep this block over resets */ } AllocSetContext; typedef AllocSetContext *AllocSet; *************** typedef struct AllocChunkData *** 221,227 **** 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); --- 222,227 ---- *************** static void AllocSetCheck(MemoryContext *** 236,246 **** /* * This is the virtual function table for AllocSet contexts. */ ! static MemoryContextMethods AllocSetMethods = { AllocSetAlloc, AllocSetFree, AllocSetRealloc, - AllocSetInit, AllocSetReset, AllocSetDelete, AllocSetGetChunkSpace, --- 236,245 ---- /* * This is the virtual function table for AllocSet contexts. */ ! static const MemoryContextMethods AllocSetMethods = { AllocSetAlloc, AllocSetFree, AllocSetRealloc, AllocSetReset, AllocSetDelete, AllocSetGetChunkSpace, *************** AllocSetFreeIndex(Size size) *** 325,351 **** /* ! * AllocSetContextCreate * 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) * 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. * Most callers should abstract the context size parameters using a macro * such as ALLOCSET_DEFAULT_SIZES. */ MemoryContext ! AllocSetContextCreate(MemoryContext parent, ! const char *name, ! Size minContextSize, ! Size initBlockSize, ! Size maxBlockSize) { AllocSet set; /* Assert we padded AllocChunkData properly */ StaticAssertStmt(ALLOC_CHUNKHDRSZ == MAXALIGN(ALLOC_CHUNKHDRSZ), --- 324,357 ---- /* ! * 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_OPTION_XXX flags * minContextSize: minimum context size * initBlockSize: initial allocation block size * maxBlockSize: maximum allocation block size * ! * Notes: if flags & MEMCONTEXT_OPTION_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. */ MemoryContext ! AllocSetContextCreateExtended(MemoryContext parent, ! const char *name, ! int flags, ! Size minContextSize, ! Size initBlockSize, ! Size maxBlockSize) { + Size headerSize; + Size firstBlockSize; AllocSet set; + AllocBlock block; /* Assert we padded AllocChunkData properly */ StaticAssertStmt(ALLOC_CHUNKHDRSZ == MAXALIGN(ALLOC_CHUNKHDRSZ), *************** AllocSetContextCreate(MemoryContext pare *** 370,390 **** 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 */ set->initBlockSize = initBlockSize; set->maxBlockSize = maxBlockSize; set->nextBlockSize = initBlockSize; /* * Compute the allocation chunk size limit for this context. It can't be --- 376,440 ---- maxBlockSize); if (minContextSize != 0 && (minContextSize != MAXALIGN(minContextSize) || ! minContextSize < 1024 || ! minContextSize > maxBlockSize)) elog(ERROR, "invalid minContextSize for memory context: %zu", minContextSize); ! /* Size of the memory context header, including name storage if needed */ ! if (flags & MEMCONTEXT_OPTION_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) ! { ! 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 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; ! 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, block->endptr - block->freeptr); ! ! /* 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; /* * Compute the allocation chunk size limit for this context. It can't be *************** AllocSetContextCreate(MemoryContext pare *** 410,483 **** (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); ! } 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. * * 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. */ static void AllocSetReset(MemoryContext context) --- 460,489 ---- (Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION)) set->allocChunkLimit >>= 1; ! /* Finally, do the type-independent part of context creation */ ! MemoryContextCreate((MemoryContext) set, ! T_AllocSetContext, ! headerSize, ! sizeof(AllocSetContext), ! &AllocSetMethods, ! parent, ! name, ! flags); return (MemoryContext) set; } /* * AllocSetReset * Frees all memory which is allocated in the given set. * * 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 give back all but the "keeper" block (which we must keep, since ! * it also holds 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) *************** AllocSetReset(MemoryContext context) *** 497,503 **** block = set->blocks; ! /* New blocks list is either empty or just the keeper block */ set->blocks = set->keeper; while (block != NULL) --- 503,509 ---- block = set->blocks; ! /* New blocks list will be just the keeper block */ set->blocks = set->keeper; while (block != NULL) *************** AllocSetReset(MemoryContext context) *** 540,546 **** * 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) --- 546,551 ---- *************** AllocSetDelete(MemoryContext context) *** 555,565 **** AllocSetCheck(context); #endif ! /* Make it look empty, just in case... */ ! MemSetAligned(set->freelist, 0, sizeof(set->freelist)); ! set->blocks = NULL; ! set->keeper = NULL; ! while (block != NULL) { AllocBlock next = block->next; --- 560,566 ---- AllocSetCheck(context); #endif ! /* Free all blocks except the keeper */ while (block != NULL) { AllocBlock next = block->next; *************** AllocSetDelete(MemoryContext context) *** 567,575 **** #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif ! free(block); block = next; } } /* --- 568,583 ---- #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif ! ! /* Free the block, unless it's the keeper */ ! if (block != set->keeper) ! free(block); ! block = next; } + + /* Finally, free the context header + keeper block */ + free(set); } /* *************** AllocSetAlloc(MemoryContext context, Siz *** 807,824 **** 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); --- 815,820 ---- *************** AllocSetStats(MemoryContext context, int *** 1205,1215 **** AllocSet set = (AllocSet) context; Size nblocks = 0; Size freechunks = 0; ! Size totalspace = 0; Size freespace = 0; AllocBlock block; int fidx; for (block = set->blocks; block != NULL; block = block->next) { nblocks++; --- 1201,1214 ---- AllocSet set = (AllocSet) context; Size nblocks = 0; Size freechunks = 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++; *************** static void *** 1264,1270 **** AllocSetCheck(MemoryContext context) { AllocSet set = (AllocSet) context; ! char *name = set->header.name; AllocBlock prevblock; AllocBlock block; --- 1263,1269 ---- AllocSetCheck(MemoryContext context) { AllocSet set = (AllocSet) context; ! const char *name = set->header.name; AllocBlock prevblock; AllocBlock block; diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c index 19390fa..99196f6 100644 *** a/src/backend/utils/mmgr/generation.c --- b/src/backend/utils/mmgr/generation.c *************** typedef struct GenerationContext *** 61,66 **** --- 61,67 ---- /* 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 */ *************** struct GenerationChunk *** 149,155 **** 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); --- 150,155 ---- *************** static void GenerationCheck(MemoryContex *** 164,174 **** /* * This is the virtual function table for Generation contexts. */ ! static MemoryContextMethods GenerationMethods = { GenerationAlloc, GenerationFree, GenerationRealloc, - GenerationInit, GenerationReset, GenerationDelete, GenerationGetChunkSpace, --- 164,173 ---- /* * This is the virtual function table for Generation contexts. */ ! static const MemoryContextMethods GenerationMethods = { GenerationAlloc, GenerationFree, GenerationRealloc, GenerationReset, GenerationDelete, GenerationGetChunkSpace, *************** static MemoryContextMethods GenerationMe *** 208,215 **** --- 207,216 ---- MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, + int flags, Size blockSize) { + Size headerSize; GenerationContext *set; /* Assert we padded GenerationChunk properly */ *************** GenerationContextCreate(MemoryContext pa *** 231,259 **** 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); ! set->blockSize = blockSize; ! return (MemoryContext) set; ! } ! /* ! * GenerationInit ! * Context-type-specific initialization routine. ! */ ! static void ! GenerationInit(MemoryContext context) ! { ! GenerationContext *set = (GenerationContext *) context; set->block = NULL; dlist_init(&set->blocks); } /* --- 232,282 ---- elog(ERROR, "invalid blockSize for memory context: %zu", blockSize); ! /* ! * Allocate the context header. Unlike aset.c, we don't try to put this ! * into the first regular block, since that would prevent us from freeing ! * the first generation of allocations. ! */ ! /* Size of the memory context header, including name storage if needed */ ! if (flags & MEMCONTEXT_OPTION_COPY_NAME) ! headerSize = MAXALIGN(sizeof(GenerationContext) + strlen(name) + 1); ! else ! headerSize = MAXALIGN(sizeof(GenerationContext)); ! 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))); ! } ! /* ! * 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; } /* *************** GenerationReset(MemoryContext context) *** 296,311 **** /* * 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. */ static void GenerationDelete(MemoryContext context) { ! /* just reset to release all the GenerationBlocks */ GenerationReset(context); ! /* we are not responsible for deleting the context node itself */ } /* --- 319,333 ---- /* * GenerationDelete ! * Free all memory which is allocated in the given context. */ static void GenerationDelete(MemoryContext context) { ! /* Reset to release all the GenerationBlocks */ GenerationReset(context); ! /* And free the context header */ ! free(context); } /* *************** GenerationIsEmpty(MemoryContext context) *** 659,665 **** /* * GenerationStats ! * Compute stats about memory consumption of an Generation. * * level: recursion level (0 at top level); used for print indentation. * print: true to print stats to stderr. --- 681,687 ---- /* * GenerationStats ! * 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. *************** GenerationStats(MemoryContext context, i *** 676,685 **** Size nblocks = 0; Size nchunks = 0; Size nfreechunks = 0; ! Size totalspace = 0; Size freespace = 0; dlist_iter iter; dlist_foreach(iter, &set->blocks) { GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); --- 698,710 ---- Size nblocks = 0; Size nchunks = 0; Size nfreechunks = 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); *************** static void *** 727,733 **** GenerationCheck(MemoryContext context) { GenerationContext *gen = (GenerationContext *) context; ! char *name = context->name; dlist_iter iter; /* walk all blocks in this context */ --- 752,758 ---- GenerationCheck(MemoryContext context) { GenerationContext *gen = (GenerationContext *) context; ! const char *name = context->name; dlist_iter iter; /* walk all blocks in this context */ diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index c5c311f..2e4b692 100644 *** a/src/backend/utils/mmgr/mcxt.c --- b/src/backend/utils/mmgr/mcxt.c *************** MemoryContextInit(void) *** 91,99 **** 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.) */ TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", --- 91,97 ---- AssertState(TopMemoryContext == NULL); /* ! * First, initialize TopMemoryContext, which is the parent of all others. */ TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", *************** MemoryContextInit(void) *** 118,128 **** * 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); MemoryContextAllowInCriticalSection(ErrorContext, true); } --- 116,127 ---- * This should be the last step in this function, as elog.c assumes memory * management works once ErrorContext is non-null. */ ! ErrorContext = AllocSetContextCreateExtended(TopMemoryContext, ! "ErrorContext", ! 0, ! 8 * 1024, ! 8 * 1024, ! 8 * 1024); MemoryContextAllowInCriticalSection(ErrorContext, true); } *************** MemoryContextResetChildren(MemoryContext *** 191,200 **** * 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. */ void MemoryContextDelete(MemoryContext context) --- 190,198 ---- * Delete a context and its descendants, and release all space * allocated therein. * ! * 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) *************** MemoryContextDelete(MemoryContext contex *** 223,230 **** MemoryContextSetParent(context, NULL); context->methods->delete_context(context); VALGRIND_DESTROY_MEMPOOL(context); - pfree(context); } /* --- 221,228 ---- MemoryContextSetParent(context, NULL); context->methods->delete_context(context); + VALGRIND_DESTROY_MEMPOOL(context); } /* *************** MemoryContextContains(MemoryContext cont *** 587,686 **** 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 * 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. * ! * Note that the name field of a MemoryContext does not point to ! * separately-allocated storage, so it should not be freed at context ! * deletion. ! *-------------------- */ ! MemoryContext ! MemoryContextCreate(NodeTag tag, Size size, ! MemoryContextMethods *methods, MemoryContext parent, ! const char *name) { ! MemoryContext node; ! Size needed = size + strlen(name) + 1; ! ! /* 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); ! } ! /* Initialize the node as best we can */ ! MemSet(node, 0, size); node->type = tag; node->methods = methods; ! node->parent = NULL; /* for the moment */ node->firstchild = NULL; node->prevchild = NULL; ! node->nextchild = NULL; ! node->isReset = true; ! node->name = ((char *) node) + size; ! strcpy(node->name, name); ! /* Type-specific routine finishes any other essential initialization */ ! node->methods->init(node); ! /* OK to link node to parent (if any) */ ! /* Could use MemoryContextSetParent here, but doesn't seem worthwhile */ if (parent) { - node->parent = parent; node->nextchild = parent->firstchild; if (parent->firstchild != NULL) parent->firstchild->prevchild = node; --- 585,669 ---- 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 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. * ! * 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. ! * 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_OPTION_XXX flags. * ! * Context routines generally assume that MemoryContextCreate can't fail, ! * so this can contain Assert but not elog/ereport. */ ! void ! MemoryContextCreate(MemoryContext node, ! NodeTag tag, Size size, Size nameoffset, ! const MemoryContextMethods *methods, MemoryContext parent, ! const char *name, ! int flags) { ! /* Creating new memory contexts is not allowed in a critical section */ Assert(CritSectionCount == 0); ! /* Check size is sane */ ! Assert(nameoffset >= sizeof(MemoryContextData)); ! Assert((flags & MEMCONTEXT_OPTION_COPY_NAME) ? ! size >= nameoffset + strlen(name) + 1 : ! size >= nameoffset); ! /* Initialize all standard fields of memory context header */ node->type = tag; + node->isReset = true; node->methods = methods; ! node->parent = parent; node->firstchild = NULL; node->prevchild = NULL; ! node->reset_cbs = NULL; ! if (flags & MEMCONTEXT_OPTION_COPY_NAME) ! { ! /* Insert context name into space reserved for it */ ! char *namecopy = ((char *) node) + nameoffset; ! 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->nextchild = parent->firstchild; if (parent->firstchild != NULL) parent->firstchild->prevchild = node; *************** MemoryContextCreate(NodeTag tag, Size si *** 688,698 **** /* inherit allowInCritSection flag from parent */ node->allowInCritSection = parent->allowInCritSection; } VALGRIND_CREATE_MEMPOOL(node, 0, false); - - /* Return to type-specific creation routine to finish up */ - return node; } /* --- 671,683 ---- /* inherit allowInCritSection flag from parent */ node->allowInCritSection = parent->allowInCritSection; } + else + { + node->nextchild = NULL; + node->allowInCritSection = false; + } VALGRIND_CREATE_MEMPOOL(node, 0, false); } /* diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c index ee21752..91e154a 100644 *** a/src/backend/utils/mmgr/slab.c --- b/src/backend/utils/mmgr/slab.c *************** typedef struct SlabContext *** 67,72 **** --- 67,73 ---- 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 */ *************** typedef struct SlabChunk *** 126,132 **** 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); --- 127,132 ---- *************** static void SlabCheck(MemoryContext cont *** 140,150 **** /* * This is the virtual function table for Slab contexts. */ ! static MemoryContextMethods SlabMethods = { SlabAlloc, SlabFree, SlabRealloc, - SlabInit, SlabReset, SlabDelete, SlabGetChunkSpace, --- 140,149 ---- /* * This is the virtual function table for Slab contexts. */ ! static const MemoryContextMethods SlabMethods = { SlabAlloc, SlabFree, SlabRealloc, SlabReset, SlabDelete, SlabGetChunkSpace, *************** static MemoryContextMethods SlabMethods *** 177,200 **** * Create a new Slab context. * * parent: parent context, or NULL if top-level context ! * name: name of context (for debugging --- string will be copied) * blockSize: allocation block size * chunkSize: allocation chunk size * * The chunkSize may not exceed: * MAXALIGN_DOWN(SIZE_MAX) - MAXALIGN(sizeof(SlabBlock)) - SLAB_CHUNKHDRSZ - * */ MemoryContext SlabContextCreate(MemoryContext parent, const char *name, Size blockSize, Size chunkSize) { int chunksPerBlock; Size fullChunkSize; Size freelistSize; SlabContext *slab; /* Assert we padded SlabChunk properly */ StaticAssertStmt(sizeof(SlabChunk) == MAXALIGN(sizeof(SlabChunk)), --- 176,206 ---- * Create a new Slab 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_OPTION_XXX flags * blockSize: allocation block size * chunkSize: allocation chunk size * + * Notes: if flags & MEMCONTEXT_OPTION_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)), *************** SlabContextCreate(MemoryContext parent, *** 227,265 **** /* make sure the chunks actually fit on the block */ Assert((fullChunkSize * chunksPerBlock) + sizeof(SlabBlock) <= blockSize); ! /* Do the type-independent part of context creation */ ! slab = (SlabContext *) ! MemoryContextCreate(T_SlabContext, ! (offsetof(SlabContext, freelist) + freelistSize), ! &SlabMethods, ! parent, ! name); ! slab->blockSize = blockSize; slab->chunkSize = chunkSize; slab->fullChunkSize = fullChunkSize; 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); /* initialize the freelist slots */ for (i = 0; i < (slab->chunksPerBlock + 1); i++) dlist_init(&slab->freelist[i]); } /* --- 233,290 ---- /* make sure the chunks actually fit on the block */ Assert((fullChunkSize * chunksPerBlock) + sizeof(SlabBlock) <= blockSize); ! /* ! * Allocate the context header. Unlike aset.c, we don't try to put this ! * into the first regular block; not worth the extra complication. ! */ ! /* Size of the memory context header, including name storage if needed */ ! nameOffset = offsetof(SlabContext, freelist) + freelistSize; ! if (flags & MEMCONTEXT_OPTION_COPY_NAME) ! headerSize = nameOffset + strlen(name) + 1; ! else ! headerSize = nameOffset; ! ! 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))); ! } ! ! /* ! * 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->minFreeChunks = 0; ! 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; } /* *************** SlabReset(MemoryContext context) *** 308,321 **** /* * SlabDelete ! * Frees all memory which is allocated in the given slab, in preparation ! * for deletion of the slab. We simply call SlabReset(). */ static void SlabDelete(MemoryContext context) { ! /* just reset the context */ SlabReset(context); } /* --- 333,347 ---- /* * SlabDelete ! * Free all memory which is allocated in the given context. */ static void SlabDelete(MemoryContext context) { ! /* Reset to release all the SlabBlocks */ SlabReset(context); + /* And free the context header */ + free(context); } /* *************** SlabIsEmpty(MemoryContext context) *** 613,619 **** /* * SlabStats ! * Compute stats about memory consumption of an Slab. * * level: recursion level (0 at top level); used for print indentation. * print: true to print stats to stderr. --- 639,645 ---- /* * SlabStats ! * 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. *************** SlabStats(MemoryContext context, int lev *** 626,636 **** SlabContext *slab = castNode(SlabContext, context); Size nblocks = 0; Size freechunks = 0; ! Size totalspace = 0; Size freespace = 0; int i; ! Assert(slab); for (i = 0; i <= slab->chunksPerBlock; i++) { --- 652,663 ---- SlabContext *slab = castNode(SlabContext, context); Size nblocks = 0; Size freechunks = 0; ! Size totalspace; Size freespace = 0; int i; ! /* Include context header in totalspace */ ! totalspace = slab->headerSize; for (i = 0; i <= slab->chunksPerBlock; i++) { *************** SlabCheck(MemoryContext context) *** 682,688 **** { int i; SlabContext *slab = castNode(SlabContext, context); ! char *name = slab->header.name; char *freechunks; Assert(slab); --- 709,715 ---- { int i; SlabContext *slab = castNode(SlabContext, context); ! const char *name = slab->header.name; char *freechunks; Assert(slab); diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h index e22d9fb..c7eb1e7 100644 *** a/src/include/nodes/memnodes.h --- b/src/include/nodes/memnodes.h *************** typedef struct MemoryContextMethods *** 57,63 **** /* 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); --- 57,62 ---- *************** typedef struct MemoryContextData *** 76,87 **** /* 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 */ 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) */ MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */ } MemoryContextData; --- 75,86 ---- /* 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 */ ! 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 */ ! const char *name; /* context name (just for debugging) */ MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */ } MemoryContextData; diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index d177b0c..85901dd 100644 *** a/src/include/utils/memutils.h --- b/src/include/utils/memutils.h *************** GetMemoryChunkContext(void *pointer) *** 132,141 **** * 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, MemoryContext parent, ! const char *name); /* --- 132,143 ---- * context creation. It's intended to be called from context-type- * specific creation routines, and noplace else. */ ! extern void MemoryContextCreate(MemoryContext node, ! NodeTag tag, Size size, Size nameoffset, ! const MemoryContextMethods *methods, MemoryContext parent, ! const char *name, ! int flags); /* *************** extern MemoryContext MemoryContextCreate *** 143,166 **** */ /* aset.c */ ! extern MemoryContext AllocSetContextCreate(MemoryContext parent, ! const char *name, ! Size minContextSize, ! Size initBlockSize, ! Size maxBlockSize); /* slab.c */ extern MemoryContext SlabContextCreate(MemoryContext parent, const char *name, Size blockSize, Size chunkSize); /* generation.c */ extern MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, Size blockSize); /* * Recommended default alloc parameters, suitable for "ordinary" contexts * that might hold quite a lot of data. */ --- 145,188 ---- */ /* aset.c */ ! extern MemoryContext AllocSetContextCreateExtended(MemoryContext parent, ! const char *name, ! int flags, ! Size minContextSize, ! Size initBlockSize, ! Size maxBlockSize); ! ! /* backwards compatibility macro: only works for constant context names */ ! #ifdef HAVE__BUILTIN_CONSTANT_P ! #define AllocSetContextCreate(parent, name, allocparams) \ ! (StaticAssertExpr(__builtin_constant_p(name), \ ! "Use AllocSetContextCreateExtended with MEMCONTEXT_OPTION_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. + */ + #define MEMCONTEXT_OPTION_COPY_NAME 0x0001 /* is passed name transient? */ + + /* * Recommended default alloc parameters, suitable for "ordinary" contexts * that might hold quite a lot of data. */ diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 9f53132..d2723e0 100644 *** a/src/pl/plperl/plperl.c --- b/src/pl/plperl/plperl.c *************** compile_plperl_function(Oid fn_oid, bool *** 2777,2785 **** /************************************************************ * Allocate a context that will hold all PG data for the procedure. ************************************************************/ ! proc_cxt = AllocSetContextCreate(TopMemoryContext, ! NameStr(procStruct->proname), ! ALLOCSET_SMALL_SIZES); /************************************************************ * Allocate and fill a new procedure description block. --- 2777,2786 ---- /************************************************************ * Allocate a context that will hold all PG data for the procedure. ************************************************************/ ! proc_cxt = AllocSetContextCreateExtended(TopMemoryContext, ! NameStr(procStruct->proname), ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_SMALL_SIZES); /************************************************************ * Allocate and fill a new procedure description block. diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c index b7c24e3..4d229da 100644 *** a/src/pl/plpython/plpy_procedure.c --- b/src/pl/plpython/plpy_procedure.c *************** PLy_procedure_create(HeapTuple procTup, *** 166,174 **** } /* Create long-lived context that all procedure info will live in */ ! cxt = AllocSetContextCreate(TopMemoryContext, ! procName, ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(cxt); --- 166,175 ---- } /* Create long-lived context that all procedure info will live in */ ! cxt = AllocSetContextCreateExtended(TopMemoryContext, ! procName, ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(cxt); diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index e0792d9..fd118e5 100644 *** a/src/pl/tcl/pltcl.c --- b/src/pl/tcl/pltcl.c *************** compile_pltcl_function(Oid fn_oid, Oid t *** 1471,1479 **** * 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); /************************************************************ * Allocate and fill a new procedure description block. --- 1471,1480 ---- * Allocate a context that will hold all PG data for the procedure. * We use the internal proc name as the context name. ************************************************************/ ! proc_cxt = AllocSetContextCreateExtended(TopMemoryContext, ! internal_proname, ! MEMCONTEXT_OPTION_COPY_NAME, ! ALLOCSET_SMALL_SIZES); /************************************************************ * Allocate and fill a new procedure description block.
On 11 December 2017 at 17:38, Tom Lane <tgl@sss.pgh.pa.us> wrote: > Simon Riggs <simon@2ndquadrant.com> writes: >> On 11 December 2017 at 16:27, Tom Lane <tgl@sss.pgh.pa.us> wrote: >>> For a *very* large majority of the callers of AllocSetContextCreate, >>> the context name is a simple C string constant, so we could just store >>> the pointer to it and save the space and cycles required to copy it. > >> Why have the string at all in that case? > > Try reading a MemoryContextStats dump without it ... I understood. I thought you were suggesting removing it in favour of a pointer. -- Simon Riggs http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
I wrote: > I've not done any benchmarking on this yet, just confirmed that it > compiles and passes check-world. So once I'd done some benchmarking, I was a bit disappointed: I could not measure any difference anymore in "pgbench -S", and poking into other scenarios found cases that were actually slower, like do $$ begin for i in 1..10000000 loop declare x int; begin x := 42; exception when others then null; end; end loop; end$$; which went from about 12.4 seconds in HEAD to about 13.6 with my v2 patch. When I looked into the reason, I found that my earlier blithe pronouncement that "optimizing for the unused-context case seems like the wrong thing" was too simple. In this example we create and delete two memory contexts per loop (a subtransaction context and an ExprContext) and neither of them receives any palloc requests. Some of the contexts created in a "pgbench -S" scenario are the same way. So in these examples, we were on the losing side of the replace-a-palloc-with-a-malloc trade. However, if we can predict which contexts are more likely not to receive traffic, we can fix this by doing things the old way for those contexts. I instrumented AllocSetDelete to log whether the target context had received any requests in its lifespan, and aggregated the reports over a run of the core regression tests. I found these cases where there were significantly more reports of "context was never used" than "context was used": 379 CurTransactionContext was never used 24 CurTransactionContext was used 66978 ExprContext was never used 17364 ExprContext was used 993 HashTableContext was never used 25 HashTableContext was used 88 Logical Replication Launcher sublist was never used 2 Logical Replication Launcher sublist was used 11139 SPI Exec was never used 2421 SPI Exec was used 36 SetOp was never used 2 SetOp was used 185 Statistics snapshot was never used 104 Subplan HashTable Context was never used 44 Subplan HashTable Context was used 148 Subplan HashTable Temp Context was never used 1481 Table function arguments was never used 45 Table function arguments was used 22 TableFunc per value context was never used 52 Unique was never used 4 Unique was used 137 WindowAgg Aggregates was never used 2 WindowAgg Aggregates was used 127 WindowAgg Partition was never used 12 WindowAgg Partition was used 288 _bt_pagedel was never used 14 _bt_pagedel was used 35 event trigger context was never used 3 event trigger context was used 38386 ginPlaceToPage temporary context was never used 3348 ginPlaceToPage temporary context was used 454 ident parser context was never used 229 tSRF function arguments was never used 46 tSRF function arguments was used A lot of these probably aren't bothering with optimizing because they just don't occur often enough to move the needle. (And in workloads where that wasn't true, maybe the usage statistics would be different anyway.) But it looked to me like CurTransactionContext, ExprContext, HashTableContext, SPI Exec, and ginPlaceToPage temporary context would be worth doing it the old way for. Accordingly, attached is a v3 patch that adds another flag to AllocSetContextCreateExtended telling it to do things the old way with the context header in TopMemoryContext. (We can still optimize context name copying as before.) This fixes the subtransaction case I showed above, bringing it to 10.7 sec which is noticeably better than HEAD. I now see maybe a 1 or 2 percent improvement in "pgbench -S", which isn't much, but it turns out that that test case only involves half a dozen context creations/deletions per transaction. So probably we aren't going to get any noticeable movement on that benchmark, and it'd be brighter to look for test cases where more contexts are involved. regards, tom lane diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c index 868c14e..adbbc44 100644 *** a/contrib/amcheck/verify_nbtree.c --- b/contrib/amcheck/verify_nbtree.c *************** bt_check_every_level(Relation rel, bool *** 295,303 **** /* Create context for page */ state->targetcontext = AllocSetContextCreate(CurrentMemoryContext, "amcheck context", ! ALLOCSET_DEFAULT_MINSIZE, ! ALLOCSET_DEFAULT_INITSIZE, ! ALLOCSET_DEFAULT_MAXSIZE); state->checkstrategy = GetAccessStrategy(BAS_BULKREAD); /* Get true root block from meta-page */ --- 295,301 ---- /* Create context for page */ state->targetcontext = AllocSetContextCreate(CurrentMemoryContext, "amcheck context", ! ALLOCSET_DEFAULT_SIZES); state->checkstrategy = GetAccessStrategy(BAS_BULKREAD); /* Get true root block from meta-page */ diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c index 1b920fa..ce7856e 100644 *** a/src/backend/access/gin/ginbtree.c --- b/src/backend/access/gin/ginbtree.c *************** ginPlaceToPage(GinBtree btree, GinBtreeS *** 345,355 **** * We do all the work of this function and its subfunctions in a temporary * memory context. This avoids leakages and simplifies APIs, since some * subfunctions allocate storage that has to survive until we've finished ! * the WAL insertion. */ ! tmpCxt = AllocSetContextCreate(CurrentMemoryContext, ! "ginPlaceToPage temporary context", ! ALLOCSET_DEFAULT_SIZES); oldCxt = MemoryContextSwitchTo(tmpCxt); if (GinPageIsData(page)) --- 345,357 ---- * We do all the work of this function and its subfunctions in a temporary * memory context. This avoids leakages and simplifies APIs, since some * subfunctions allocate storage that has to survive until we've finished ! * the WAL insertion. However, it's not unusual for the context to go ! * unused, so tell aset.c to optimize for that case. */ ! tmpCxt = AllocSetContextCreateExtended(CurrentMemoryContext, ! "ginPlaceToPage temporary context", ! MEMCONTEXT_OPTIMIZE_EMPTY, ! ALLOCSET_DEFAULT_SIZES); oldCxt = MemoryContextSwitchTo(tmpCxt); if (GinPageIsData(page)) diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 046898c..d864be9 100644 *** a/src/backend/access/transam/xact.c --- b/src/backend/access/transam/xact.c *************** AtStart_Memory(void) *** 997,1007 **** */ if (TransactionAbortContext == NULL) TransactionAbortContext = ! AllocSetContextCreate(TopMemoryContext, ! "TransactionAbortContext", ! 32 * 1024, ! 32 * 1024, ! 32 * 1024); /* * We shouldn't have a transaction context already. --- 997,1008 ---- */ if (TransactionAbortContext == NULL) TransactionAbortContext = ! AllocSetContextCreateExtended(TopMemoryContext, ! "TransactionAbortContext", ! 0, ! 32 * 1024, ! 32 * 1024, ! 32 * 1024); /* * We shouldn't have a transaction context already. *************** AtSubStart_Memory(void) *** 1069,1078 **** * Create a CurTransactionContext, which will be used to hold data that * survives subtransaction commit but disappears on subtransaction abort. * We make it a child of the immediate parent's CurTransactionContext. */ ! CurTransactionContext = AllocSetContextCreate(CurTransactionContext, ! "CurTransactionContext", ! ALLOCSET_DEFAULT_SIZES); s->curTransactionContext = CurTransactionContext; /* Make the CurTransactionContext active. */ --- 1070,1083 ---- * Create a CurTransactionContext, which will be used to hold data that * survives subtransaction commit but disappears on subtransaction abort. * We make it a child of the immediate parent's CurTransactionContext. + * Often, nothing ever gets put in a subtransaction's context, so tell + * aset.c to optimize for that case. */ ! CurTransactionContext = ! AllocSetContextCreateExtended(CurTransactionContext, ! "CurTransactionContext", ! MEMCONTEXT_OPTIMIZE_EMPTY, ! ALLOCSET_DEFAULT_SIZES); s->curTransactionContext = CurTransactionContext; /* Make the CurTransactionContext active. */ diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index ef156e4..5c4018e 100644 *** a/src/backend/catalog/partition.c --- b/src/backend/catalog/partition.c *************** RelationBuildPartitionDesc(Relation rel) *** 520,528 **** } /* Now build the actual relcache partition descriptor */ ! rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(rel), ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt); result = (PartitionDescData *) palloc0(sizeof(PartitionDescData)); --- 520,529 ---- } /* Now build the actual relcache partition descriptor */ ! rel->rd_pdcxt = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(rel), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt); result = (PartitionDescData *) palloc0(sizeof(PartitionDescData)); diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 086a6ef..a7f426d 100644 *** a/src/backend/commands/subscriptioncmds.c --- b/src/backend/commands/subscriptioncmds.c *************** publicationListToArray(List *publist) *** 259,267 **** /* Create memory context for temporary allocations. */ memcxt = AllocSetContextCreate(CurrentMemoryContext, "publicationListToArray to array", ! ALLOCSET_DEFAULT_MINSIZE, ! ALLOCSET_DEFAULT_INITSIZE, ! ALLOCSET_DEFAULT_MAXSIZE); oldcxt = MemoryContextSwitchTo(memcxt); datums = (Datum *) palloc(sizeof(Datum) * list_length(publist)); --- 259,265 ---- /* Create memory context for temporary allocations. */ memcxt = AllocSetContextCreate(CurrentMemoryContext, "publicationListToArray to array", ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(memcxt); datums = (Datum *) palloc(sizeof(Datum) * list_length(publist)); diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 8764398..cc7b81a 100644 *** a/src/backend/executor/execUtils.c --- b/src/backend/executor/execUtils.c *************** CreateExprContext(EState *estate) *** 241,252 **** econtext->ecxt_per_query_memory = estate->es_query_cxt; /* ! * Create working memory for expression evaluation in this context. */ econtext->ecxt_per_tuple_memory = ! AllocSetContextCreate(estate->es_query_cxt, ! "ExprContext", ! ALLOCSET_DEFAULT_SIZES); econtext->ecxt_param_exec_vals = estate->es_param_exec_vals; econtext->ecxt_param_list_info = estate->es_param_list_info; --- 241,255 ---- econtext->ecxt_per_query_memory = estate->es_query_cxt; /* ! * Create working memory for expression evaluation in this context. If ! * the expression uses only pass-by-value data types, the context may well ! * go unused, so tell aset.c to optimize for that case. */ econtext->ecxt_per_tuple_memory = ! AllocSetContextCreateExtended(estate->es_query_cxt, ! "ExprContext", ! MEMCONTEXT_OPTIMIZE_EMPTY, ! ALLOCSET_DEFAULT_SIZES); econtext->ecxt_param_exec_vals = estate->es_param_exec_vals; econtext->ecxt_param_list_info = estate->es_param_list_info; *************** CreateStandaloneExprContext(void) *** 313,321 **** * Create working memory for expression evaluation in this context. */ econtext->ecxt_per_tuple_memory = ! AllocSetContextCreate(CurrentMemoryContext, ! "ExprContext", ! ALLOCSET_DEFAULT_SIZES); econtext->ecxt_param_exec_vals = NULL; econtext->ecxt_param_list_info = NULL; --- 316,325 ---- * Create working memory for expression evaluation in this context. */ econtext->ecxt_per_tuple_memory = ! AllocSetContextCreateExtended(CurrentMemoryContext, ! "Standalone ExprContext", ! MEMCONTEXT_OPTIMIZE_EMPTY, ! ALLOCSET_DEFAULT_SIZES); econtext->ecxt_param_exec_vals = NULL; econtext->ecxt_param_list_info = NULL; diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c index 6fe5d69..8c30f8c 100644 *** a/src/backend/executor/nodeHash.c --- b/src/backend/executor/nodeHash.c *************** ExecHashTableCreate(Hash *node, List *ha *** 337,347 **** /* * Create temporary memory contexts in which to keep the hashtable working ! * storage. See notes in executor/hashjoin.h. */ ! hashtable->hashCxt = AllocSetContextCreate(CurrentMemoryContext, ! "HashTableContext", ! ALLOCSET_DEFAULT_SIZES); hashtable->batchCxt = AllocSetContextCreate(hashtable->hashCxt, "HashBatchContext", --- 337,351 ---- /* * Create temporary memory contexts in which to keep the hashtable working ! * storage. See notes in executor/hashjoin.h. If we are only expecting ! * one batch, it's likely that hashCxt will never receive any allocations, ! * so tell aset.c to optimize for that case. */ ! hashtable->hashCxt = ! AllocSetContextCreateExtended(CurrentMemoryContext, ! "HashTableContext", ! (nbatch > 1) ? 0 : MEMCONTEXT_OPTIMIZE_EMPTY, ! ALLOCSET_DEFAULT_SIZES); hashtable->batchCxt = AllocSetContextCreate(hashtable->hashCxt, "HashBatchContext", diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index f3da2dd..dd461cd 100644 *** a/src/backend/executor/spi.c --- b/src/backend/executor/spi.c *************** SPI_connect(void) *** 130,144 **** * * XXX it would be better to use PortalContext as the parent context, but * we may not be inside a portal (consider deferred-trigger execution). ! * Perhaps CurTransactionContext would do? For now it doesn't matter ! * because we clean up explicitly in AtEOSubXact_SPI(). */ _SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext, "SPI Proc", ALLOCSET_DEFAULT_SIZES); ! _SPI_current->execCxt = AllocSetContextCreate(TopTransactionContext, ! "SPI Exec", ! ALLOCSET_DEFAULT_SIZES); /* ... and switch to procedure's context */ _SPI_current->savedcxt = MemoryContextSwitchTo(_SPI_current->procCxt); --- 130,147 ---- * * XXX it would be better to use PortalContext as the parent context, but * we may not be inside a portal (consider deferred-trigger execution). ! * Perhaps CurTransactionContext would do? For now it doesn't matter ! * because we clean up explicitly in AtEOSubXact_SPI(). In practice, it ! * seems that execCxt often goes unused, so optimize for that. */ _SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext, "SPI Proc", ALLOCSET_DEFAULT_SIZES); ! _SPI_current->execCxt = ! AllocSetContextCreateExtended(TopTransactionContext, ! "SPI Exec", ! MEMCONTEXT_OPTIMIZE_EMPTY, ! ALLOCSET_DEFAULT_SIZES); /* ... and switch to procedure's context */ _SPI_current->savedcxt = MemoryContextSwitchTo(_SPI_current->procCxt); diff --git a/src/backend/lib/knapsack.c b/src/backend/lib/knapsack.c index ddf2b9a..490c0cc 100644 *** a/src/backend/lib/knapsack.c --- b/src/backend/lib/knapsack.c *************** DiscreteKnapsack(int max_weight, int num *** 57,65 **** { MemoryContext local_ctx = AllocSetContextCreate(CurrentMemoryContext, "Knapsack", ! ALLOCSET_SMALL_MINSIZE, ! ALLOCSET_SMALL_INITSIZE, ! ALLOCSET_SMALL_MAXSIZE); MemoryContext oldctx = MemoryContextSwitchTo(local_ctx); double *values; Bitmapset **sets; --- 57,63 ---- { MemoryContext local_ctx = AllocSetContextCreate(CurrentMemoryContext, "Knapsack", ! ALLOCSET_SMALL_SIZES); MemoryContext oldctx = MemoryContextSwitchTo(local_ctx); double *values; Bitmapset **sets; diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c index a613ef4..24be3ce 100644 *** a/src/backend/replication/logical/launcher.c --- b/src/backend/replication/logical/launcher.c *************** ApplyLauncherMain(Datum main_arg) *** 925,933 **** /* 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); oldctx = MemoryContextSwitchTo(subctx); /* search for subscriptions to start or stop. */ --- 925,931 ---- /* Use temporary context for the database list and worker info. */ subctx = AllocSetContextCreate(TopMemoryContext, "Logical Replication Launcher sublist", ! ALLOCSET_DEFAULT_SIZES); oldctx = MemoryContextSwitchTo(subctx); /* search for subscriptions to start or stop. */ diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index fa95bab..5ac391d 100644 *** a/src/backend/replication/logical/reorderbuffer.c --- b/src/backend/replication/logical/reorderbuffer.c *************** ReorderBufferAllocate(void) *** 237,252 **** --- 237,255 ---- 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); diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index c312654..550b156 100644 *** a/src/backend/replication/pgoutput/pgoutput.c --- b/src/backend/replication/pgoutput/pgoutput.c *************** pgoutput_startup(LogicalDecodingContext *** 152,160 **** /* 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); ctx->output_plugin_private = data; --- 152,158 ---- /* Create our memory context for private allocations. */ data->context = AllocSetContextCreate(ctx->context, "logical replication output context", ! ALLOCSET_DEFAULT_SIZES); ctx->output_plugin_private = data; diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 12a5f15..3a9233e 100644 *** a/src/backend/utils/cache/relcache.c --- b/src/backend/utils/cache/relcache.c *************** RelationBuildRuleLock(Relation relation) *** 669,677 **** /* * Make the private context. Assume it'll not contain much data. */ ! rulescxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(relation), ! ALLOCSET_SMALL_SIZES); relation->rd_rulescxt = rulescxt; /* --- 669,678 ---- /* * Make the private context. Assume it'll not contain much data. */ ! rulescxt = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(relation), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); relation->rd_rulescxt = rulescxt; /* *************** RelationBuildPartitionKey(Relation relat *** 984,992 **** ReleaseSysCache(tuple); /* Success --- now copy to the cache memory */ ! partkeycxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(relation), ! ALLOCSET_SMALL_SIZES); relation->rd_partkeycxt = partkeycxt; oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt); relation->rd_partkey = copy_partition_key(key); --- 985,994 ---- ReleaseSysCache(tuple); /* Success --- now copy to the cache memory */ ! 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); *************** RelationInitIndexAccessInfo(Relation rel *** 1566,1574 **** * 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); relation->rd_indexcxt = indexcxt; /* --- 1568,1577 ---- * 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 = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(relation), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); relation->rd_indexcxt = indexcxt; /* *************** load_relcache_init_file(bool shared) *** 5537,5545 **** * prepare index info context --- parameters should match * RelationInitIndexAccessInfo */ ! indexcxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(rel), ! ALLOCSET_SMALL_SIZES); rel->rd_indexcxt = indexcxt; /* --- 5540,5550 ---- * prepare index info context --- parameters should match * RelationInitIndexAccessInfo */ ! indexcxt = ! AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(rel), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); rel->rd_indexcxt = indexcxt; /* diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c index da5c8ea..29cf93a 100644 *** a/src/backend/utils/cache/ts_cache.c --- b/src/backend/utils/cache/ts_cache.c *************** lookup_ts_dictionary_cache(Oid dictId) *** 294,302 **** 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); } else { --- 294,303 ---- Assert(!found); /* it wasn't there a moment ago */ /* Create private memory context the first time through */ ! saveCtx = AllocSetContextCreateExtended(CacheMemoryContext, ! NameStr(dict->dictname), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); } else { diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index 71f5f06..c88efc3 100644 *** a/src/backend/utils/hash/dynahash.c --- b/src/backend/utils/hash/dynahash.c *************** hash_create(const char *tabname, long ne *** 340,348 **** CurrentDynaHashCxt = info->hcxt; else CurrentDynaHashCxt = TopMemoryContext; ! CurrentDynaHashCxt = AllocSetContextCreate(CurrentDynaHashCxt, ! tabname, ! ALLOCSET_DEFAULT_SIZES); } /* Initialize the hash header, plus a copy of the table name */ --- 340,350 ---- CurrentDynaHashCxt = info->hcxt; else CurrentDynaHashCxt = TopMemoryContext; ! CurrentDynaHashCxt = ! AllocSetContextCreateExtended(CurrentDynaHashCxt, ! tabname, ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_DEFAULT_SIZES); } /* Initialize the hash header, plus a copy of the table name */ diff --git a/src/backend/utils/mmgr/README b/src/backend/utils/mmgr/README index 296fa19..f0db836 100644 *** a/src/backend/utils/mmgr/README --- b/src/backend/utils/mmgr/README *************** every other context is a direct or indir *** 177,184 **** 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 allocating stuff here unless really necessary, and especially avoid running with CurrentMemoryContext pointing here. --- 177,183 ---- 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. Avoid allocating stuff here unless really necessary, and especially avoid running with CurrentMemoryContext pointing here. diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 1bd1c34..acc15f9 100644 *** a/src/backend/utils/mmgr/aset.c --- b/src/backend/utils/mmgr/aset.c *************** *** 93,98 **** --- 93,101 ---- * * 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. *-------------------- */ *************** typedef struct AllocSetContext *** 127,134 **** --- 130,139 ---- 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 */ + bool separateHeader; /* if F, header + keeper block are 1 malloc */ } AllocSetContext; typedef AllocSetContext *AllocSet; *************** typedef struct AllocChunkData *** 221,227 **** 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); --- 226,231 ---- *************** static void AllocSetCheck(MemoryContext *** 236,246 **** /* * This is the virtual function table for AllocSet contexts. */ ! static MemoryContextMethods AllocSetMethods = { AllocSetAlloc, AllocSetFree, AllocSetRealloc, - AllocSetInit, AllocSetReset, AllocSetDelete, AllocSetGetChunkSpace, --- 240,249 ---- /* * This is the virtual function table for AllocSet contexts. */ ! static const MemoryContextMethods AllocSetMethods = { AllocSetAlloc, AllocSetFree, AllocSetRealloc, AllocSetReset, AllocSetDelete, AllocSetGetChunkSpace, *************** AllocSetFreeIndex(Size size) *** 325,350 **** /* ! * AllocSetContextCreate * 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) ! * 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. * Most callers should abstract the context size parameters using a macro ! * such as ALLOCSET_DEFAULT_SIZES. */ MemoryContext ! AllocSetContextCreate(MemoryContext parent, ! const char *name, ! Size minContextSize, ! Size initBlockSize, ! Size maxBlockSize) { AllocSet set; /* Assert we padded AllocChunkData properly */ --- 328,358 ---- /* ! * 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 (ignored if MEMCONTEXT_OPTIMIZE_EMPTY) * initBlockSize: initial allocation block size * maxBlockSize: maximum allocation block size * ! * 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. (This is now *required* when going ! * through the AllocSetContextCreate macro.) */ MemoryContext ! AllocSetContextCreateExtended(MemoryContext parent, ! const char *name, ! int flags, ! Size minContextSize, ! Size initBlockSize, ! Size maxBlockSize) { + Size headerSize; AllocSet set; /* Assert we padded AllocChunkData properly */ *************** AllocSetContextCreate(MemoryContext pare *** 370,390 **** 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 */ set->initBlockSize = initBlockSize; set->maxBlockSize = maxBlockSize; set->nextBlockSize = initBlockSize; /* * Compute the allocation chunk size limit for this context. It can't be --- 378,467 ---- maxBlockSize); if (minContextSize != 0 && (minContextSize != MAXALIGN(minContextSize) || ! minContextSize < 1024 || ! minContextSize > maxBlockSize)) elog(ERROR, "invalid minContextSize for memory context: %zu", minContextSize); ! /* 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)); ! ! /* ! * If we are optimizing for the possibility that the context is never ! * used, we allocate the context header in TopMemoryContext (which had ! * better already exist). Otherwise, create a keeper block immediately ! * and make the context header part of that same malloc request, so as to ! * save a palloc/pfree cycle. ! * ! * Avoid writing code that can fail between here and MemoryContextCreate; ! * we'd leak the header/initial block if we ereport in this stretch. ! */ ! if (flags & MEMCONTEXT_OPTIMIZE_EMPTY) ! { ! set = (AllocSet) MemoryContextAlloc(TopMemoryContext, headerSize); ! ! /* Flag header as separate, and show that there's no initial block */ ! set->separateHeader = true; ! set->blocks = NULL; ! set->keeper = NULL; ! } ! else ! { ! Size firstBlockSize; ! AllocBlock block; ! ! /* 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))); ! } ! ! /* 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); ! ! /* Mark header as part of initial block */ ! set->separateHeader = false; ! /* 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; /* * Compute the allocation chunk size limit for this context. It can't be *************** AllocSetContextCreate(MemoryContext pare *** 410,474 **** (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); ! } 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. * --- 487,506 ---- (Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION)) set->allocChunkLimit >>= 1; ! /* Finally, do the type-independent part of context creation */ ! MemoryContextCreate((MemoryContext) set, ! T_AllocSetContext, ! headerSize, ! sizeof(AllocSetContext), ! &AllocSetMethods, ! parent, ! name, ! flags); return (MemoryContext) set; } /* * AllocSetReset * Frees all memory which is allocated in the given set. * *************** AllocSetInit(MemoryContext context) *** 477,483 **** * 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. */ static void AllocSetReset(MemoryContext context) --- 509,517 ---- * 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. In the ! * !separateHeader case, we'd have to keep the keeper block anyway, since ! * that malloc chunk also holds the context header. */ static void AllocSetReset(MemoryContext context) *************** AllocSetReset(MemoryContext context) *** 540,546 **** * 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) --- 574,579 ---- *************** AllocSetDelete(MemoryContext context) *** 555,565 **** AllocSetCheck(context); #endif ! /* Make it look empty, just in case... */ ! MemSetAligned(set->freelist, 0, sizeof(set->freelist)); ! set->blocks = NULL; ! set->keeper = NULL; ! while (block != NULL) { AllocBlock next = block->next; --- 588,594 ---- AllocSetCheck(context); #endif ! /* Free all blocks, except the keeper if it is part of context header */ while (block != NULL) { AllocBlock next = block->next; *************** AllocSetDelete(MemoryContext context) *** 567,575 **** #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif ! free(block); block = next; } } /* --- 596,613 ---- #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif ! ! if (block != set->keeper || set->separateHeader) ! free(block); ! block = next; } + + /* Finally, free the context header, which may include the keeper block */ + if (set->separateHeader) + pfree(set); + else + free(set); } /* *************** AllocSetAlloc(MemoryContext context, Siz *** 811,820 **** * 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; --- 849,858 ---- * 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 MEMCONTEXT_OPTIMIZE_EMPTY; ! * 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; *************** AllocSetStats(MemoryContext context, int *** 1210,1215 **** --- 1248,1260 ---- AllocBlock block; int fidx; + /* + * Include context header in totalspace, unless it's separately allocated, + * in which case it'll have been counted in TopMemoryContext. + */ + if (!set->separateHeader) + totalspace += set->headerSize; + for (block = set->blocks; block != NULL; block = block->next) { nblocks++; *************** static void *** 1264,1270 **** AllocSetCheck(MemoryContext context) { AllocSet set = (AllocSet) context; ! char *name = set->header.name; AllocBlock prevblock; AllocBlock block; --- 1309,1315 ---- AllocSetCheck(MemoryContext context) { AllocSet set = (AllocSet) context; ! const char *name = set->header.name; AllocBlock prevblock; AllocBlock block; diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c index 19390fa..10d0fc1 100644 *** a/src/backend/utils/mmgr/generation.c --- b/src/backend/utils/mmgr/generation.c *************** typedef struct GenerationContext *** 61,66 **** --- 61,67 ---- /* 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 */ *************** struct GenerationChunk *** 149,155 **** 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); --- 150,155 ---- *************** static void GenerationCheck(MemoryContex *** 164,174 **** /* * This is the virtual function table for Generation contexts. */ ! static MemoryContextMethods GenerationMethods = { GenerationAlloc, GenerationFree, GenerationRealloc, - GenerationInit, GenerationReset, GenerationDelete, GenerationGetChunkSpace, --- 164,173 ---- /* * This is the virtual function table for Generation contexts. */ ! static const MemoryContextMethods GenerationMethods = { GenerationAlloc, GenerationFree, GenerationRealloc, GenerationReset, GenerationDelete, GenerationGetChunkSpace, *************** static MemoryContextMethods GenerationMe *** 208,215 **** --- 207,216 ---- MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, + int flags, Size blockSize) { + Size headerSize; GenerationContext *set; /* Assert we padded GenerationChunk properly */ *************** GenerationContextCreate(MemoryContext pa *** 231,259 **** 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); ! set->blockSize = blockSize; ! return (MemoryContext) set; ! } ! /* ! * GenerationInit ! * Context-type-specific initialization routine. ! */ ! static void ! GenerationInit(MemoryContext context) ! { ! GenerationContext *set = (GenerationContext *) context; set->block = NULL; dlist_init(&set->blocks); } /* --- 232,282 ---- elog(ERROR, "invalid blockSize for memory context: %zu", blockSize); ! /* ! * 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. ! */ ! /* 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)); ! 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))); ! } ! /* ! * 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; } /* *************** GenerationReset(MemoryContext context) *** 296,311 **** /* * 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. */ static void GenerationDelete(MemoryContext context) { ! /* just reset to release all the GenerationBlocks */ GenerationReset(context); ! /* we are not responsible for deleting the context node itself */ } /* --- 319,333 ---- /* * GenerationDelete ! * Free all memory which is allocated in the given context. */ static void GenerationDelete(MemoryContext context) { ! /* Reset to release all the GenerationBlocks */ GenerationReset(context); ! /* And free the context header */ ! free(context); } /* *************** GenerationIsEmpty(MemoryContext context) *** 659,665 **** /* * GenerationStats ! * Compute stats about memory consumption of an Generation. * * level: recursion level (0 at top level); used for print indentation. * print: true to print stats to stderr. --- 681,687 ---- /* * GenerationStats ! * 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. *************** GenerationStats(MemoryContext context, i *** 676,685 **** Size nblocks = 0; Size nchunks = 0; Size nfreechunks = 0; ! Size totalspace = 0; Size freespace = 0; dlist_iter iter; dlist_foreach(iter, &set->blocks) { GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); --- 698,710 ---- Size nblocks = 0; Size nchunks = 0; Size nfreechunks = 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); *************** static void *** 727,733 **** GenerationCheck(MemoryContext context) { GenerationContext *gen = (GenerationContext *) context; ! char *name = context->name; dlist_iter iter; /* walk all blocks in this context */ --- 752,758 ---- GenerationCheck(MemoryContext context) { GenerationContext *gen = (GenerationContext *) context; ! const char *name = context->name; dlist_iter iter; /* walk all blocks in this context */ diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index c5c311f..97382a6 100644 *** a/src/backend/utils/mmgr/mcxt.c --- b/src/backend/utils/mmgr/mcxt.c *************** MemoryContextInit(void) *** 91,99 **** 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.) */ TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", --- 91,97 ---- AssertState(TopMemoryContext == NULL); /* ! * First, initialize TopMemoryContext, which is the parent of all others. */ TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", *************** MemoryContextInit(void) *** 118,128 **** * 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); MemoryContextAllowInCriticalSection(ErrorContext, true); } --- 116,127 ---- * This should be the last step in this function, as elog.c assumes memory * management works once ErrorContext is non-null. */ ! ErrorContext = AllocSetContextCreateExtended(TopMemoryContext, ! "ErrorContext", ! 0, ! 8 * 1024, ! 8 * 1024, ! 8 * 1024); MemoryContextAllowInCriticalSection(ErrorContext, true); } *************** MemoryContextResetChildren(MemoryContext *** 191,200 **** * 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. */ void MemoryContextDelete(MemoryContext context) --- 190,198 ---- * Delete a context and its descendants, and release all space * allocated therein. * ! * 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) *************** MemoryContextDelete(MemoryContext contex *** 205,211 **** /* And not CurrentMemoryContext, either */ Assert(context != CurrentMemoryContext); ! MemoryContextDeleteChildren(context); /* * It's not entirely clear whether 'tis better to do this before or after --- 203,211 ---- /* And not CurrentMemoryContext, either */ Assert(context != CurrentMemoryContext); ! /* 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 *************** MemoryContextDelete(MemoryContext contex *** 223,230 **** MemoryContextSetParent(context, NULL); context->methods->delete_context(context); VALGRIND_DESTROY_MEMPOOL(context); - pfree(context); } /* --- 223,230 ---- MemoryContextSetParent(context, NULL); context->methods->delete_context(context); + VALGRIND_DESTROY_MEMPOOL(context); } /* *************** MemoryContextContains(MemoryContext cont *** 587,686 **** 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 * 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. * ! * Note that the name field of a MemoryContext does not point to ! * separately-allocated storage, so it should not be freed at context ! * deletion. ! *-------------------- */ ! MemoryContext ! MemoryContextCreate(NodeTag tag, Size size, ! MemoryContextMethods *methods, MemoryContext parent, ! const char *name) { ! MemoryContext node; ! Size needed = size + strlen(name) + 1; ! ! /* 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); ! } ! /* Initialize the node as best we can */ ! MemSet(node, 0, size); node->type = tag; node->methods = methods; ! node->parent = NULL; /* for the moment */ node->firstchild = NULL; node->prevchild = NULL; ! node->nextchild = NULL; ! node->isReset = true; ! node->name = ((char *) node) + size; ! strcpy(node->name, name); ! /* Type-specific routine finishes any other essential initialization */ ! node->methods->init(node); ! /* OK to link node to parent (if any) */ ! /* Could use MemoryContextSetParent here, but doesn't seem worthwhile */ if (parent) { - node->parent = parent; node->nextchild = parent->firstchild; if (parent->firstchild != NULL) parent->firstchild->prevchild = node; --- 587,671 ---- 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 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. * ! * 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. * ! * Context routines generally assume that MemoryContextCreate can't fail, ! * so this can contain Assert but not elog/ereport. */ ! void ! MemoryContextCreate(MemoryContext node, ! NodeTag tag, Size size, Size nameoffset, ! const MemoryContextMethods *methods, MemoryContext parent, ! const char *name, ! int flags) { ! /* Creating new memory contexts is not allowed in a critical section */ Assert(CritSectionCount == 0); ! /* Check size is sane */ ! Assert(nameoffset >= sizeof(MemoryContextData)); ! Assert((flags & MEMCONTEXT_COPY_NAME) ? ! size >= nameoffset + strlen(name) + 1 : ! size >= nameoffset); ! /* Initialize all standard fields of memory context header */ node->type = tag; + node->isReset = true; node->methods = methods; ! node->parent = parent; node->firstchild = NULL; node->prevchild = NULL; ! node->reset_cbs = NULL; ! if (flags & MEMCONTEXT_COPY_NAME) ! { ! /* Insert context name into space reserved for it */ ! char *namecopy = ((char *) node) + nameoffset; ! 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->nextchild = parent->firstchild; if (parent->firstchild != NULL) parent->firstchild->prevchild = node; *************** MemoryContextCreate(NodeTag tag, Size si *** 688,698 **** /* inherit allowInCritSection flag from parent */ node->allowInCritSection = parent->allowInCritSection; } VALGRIND_CREATE_MEMPOOL(node, 0, false); - - /* Return to type-specific creation routine to finish up */ - return node; } /* --- 673,685 ---- /* inherit allowInCritSection flag from parent */ node->allowInCritSection = parent->allowInCritSection; } + else + { + node->nextchild = NULL; + node->allowInCritSection = false; + } VALGRIND_CREATE_MEMPOOL(node, 0, false); } /* diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c index ee21752..42c0882 100644 *** a/src/backend/utils/mmgr/slab.c --- b/src/backend/utils/mmgr/slab.c *************** typedef struct SlabContext *** 67,72 **** --- 67,73 ---- 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 */ *************** typedef struct SlabChunk *** 126,132 **** 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); --- 127,132 ---- *************** static void SlabCheck(MemoryContext cont *** 140,150 **** /* * This is the virtual function table for Slab contexts. */ ! static MemoryContextMethods SlabMethods = { SlabAlloc, SlabFree, SlabRealloc, - SlabInit, SlabReset, SlabDelete, SlabGetChunkSpace, --- 140,149 ---- /* * This is the virtual function table for Slab contexts. */ ! static const MemoryContextMethods SlabMethods = { SlabAlloc, SlabFree, SlabRealloc, SlabReset, SlabDelete, SlabGetChunkSpace, *************** static MemoryContextMethods SlabMethods *** 177,200 **** * Create a new Slab context. * * parent: parent context, or NULL if top-level context ! * name: name of context (for debugging --- string will be copied) * blockSize: allocation block size * chunkSize: allocation chunk size * * The chunkSize may not exceed: * MAXALIGN_DOWN(SIZE_MAX) - MAXALIGN(sizeof(SlabBlock)) - SLAB_CHUNKHDRSZ - * */ MemoryContext SlabContextCreate(MemoryContext parent, const char *name, Size blockSize, Size chunkSize) { int chunksPerBlock; Size fullChunkSize; Size freelistSize; SlabContext *slab; /* Assert we padded SlabChunk properly */ StaticAssertStmt(sizeof(SlabChunk) == MAXALIGN(sizeof(SlabChunk)), --- 176,205 ---- * Create a new Slab 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 * 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)), *************** SlabContextCreate(MemoryContext parent, *** 227,265 **** /* make sure the chunks actually fit on the block */ Assert((fullChunkSize * chunksPerBlock) + sizeof(SlabBlock) <= blockSize); ! /* Do the type-independent part of context creation */ ! slab = (SlabContext *) ! MemoryContextCreate(T_SlabContext, ! (offsetof(SlabContext, freelist) + freelistSize), ! &SlabMethods, ! parent, ! name); ! slab->blockSize = blockSize; slab->chunkSize = chunkSize; slab->fullChunkSize = fullChunkSize; 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); /* initialize the freelist slots */ for (i = 0; i < (slab->chunksPerBlock + 1); i++) dlist_init(&slab->freelist[i]); } /* --- 232,289 ---- /* make sure the chunks actually fit on the block */ Assert((fullChunkSize * chunksPerBlock) + sizeof(SlabBlock) <= blockSize); ! /* ! * Allocate the context header. Unlike aset.c, we never try to combine ! * this with the first regular block; not worth the extra complication. ! */ ! /* 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; ! ! 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))); ! } ! ! /* ! * 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->minFreeChunks = 0; ! 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; } /* *************** SlabReset(MemoryContext context) *** 308,321 **** /* * SlabDelete ! * Frees all memory which is allocated in the given slab, in preparation ! * for deletion of the slab. We simply call SlabReset(). */ static void SlabDelete(MemoryContext context) { ! /* just reset the context */ SlabReset(context); } /* --- 332,346 ---- /* * SlabDelete ! * Free all memory which is allocated in the given context. */ static void SlabDelete(MemoryContext context) { ! /* Reset to release all the SlabBlocks */ SlabReset(context); + /* And free the context header */ + free(context); } /* *************** SlabIsEmpty(MemoryContext context) *** 613,619 **** /* * SlabStats ! * Compute stats about memory consumption of an Slab. * * level: recursion level (0 at top level); used for print indentation. * print: true to print stats to stderr. --- 638,644 ---- /* * SlabStats ! * 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. *************** SlabStats(MemoryContext context, int lev *** 626,636 **** SlabContext *slab = castNode(SlabContext, context); Size nblocks = 0; Size freechunks = 0; ! Size totalspace = 0; Size freespace = 0; int i; ! Assert(slab); for (i = 0; i <= slab->chunksPerBlock; i++) { --- 651,662 ---- SlabContext *slab = castNode(SlabContext, context); Size nblocks = 0; Size freechunks = 0; ! Size totalspace; Size freespace = 0; int i; ! /* Include context header in totalspace */ ! totalspace = slab->headerSize; for (i = 0; i <= slab->chunksPerBlock; i++) { *************** SlabCheck(MemoryContext context) *** 682,688 **** { int i; SlabContext *slab = castNode(SlabContext, context); ! char *name = slab->header.name; char *freechunks; Assert(slab); --- 708,714 ---- { int i; SlabContext *slab = castNode(SlabContext, context); ! const char *name = slab->header.name; char *freechunks; Assert(slab); diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h index e22d9fb..c7eb1e7 100644 *** a/src/include/nodes/memnodes.h --- b/src/include/nodes/memnodes.h *************** typedef struct MemoryContextMethods *** 57,63 **** /* 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); --- 57,62 ---- *************** typedef struct MemoryContextData *** 76,87 **** /* 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 */ 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) */ MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */ } MemoryContextData; --- 75,86 ---- /* 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 */ ! 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 */ ! const char *name; /* context name (just for debugging) */ MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */ } MemoryContextData; diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index d177b0c..0799259 100644 *** a/src/include/utils/memutils.h --- b/src/include/utils/memutils.h *************** GetMemoryChunkContext(void *pointer) *** 132,141 **** * 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, MemoryContext parent, ! const char *name); /* --- 132,143 ---- * context creation. It's intended to be called from context-type- * specific creation routines, and noplace else. */ ! extern void MemoryContextCreate(MemoryContext node, ! NodeTag tag, Size size, Size nameoffset, ! const MemoryContextMethods *methods, MemoryContext parent, ! const char *name, ! int flags); /* *************** extern MemoryContext MemoryContextCreate *** 143,166 **** */ /* aset.c */ ! extern MemoryContext AllocSetContextCreate(MemoryContext parent, ! const char *name, ! Size minContextSize, ! Size initBlockSize, ! Size maxBlockSize); /* slab.c */ extern MemoryContext SlabContextCreate(MemoryContext parent, const char *name, Size blockSize, Size chunkSize); /* generation.c */ extern MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, Size blockSize); /* * Recommended default alloc parameters, suitable for "ordinary" contexts * that might hold quite a lot of data. */ --- 145,195 ---- */ /* aset.c */ ! 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 + * OPTIMIZE_EMPTY: optimize for case that nothing gets allocated in context + */ + #define MEMCONTEXT_COPY_NAME 0x0001 /* is passed name transient? */ + #define MEMCONTEXT_OPTIMIZE_EMPTY 0x0002 /* expect context goes unused? */ + + /* * Recommended default alloc parameters, suitable for "ordinary" contexts * that might hold quite a lot of data. */ diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 9f53132..41fd0ba 100644 *** a/src/pl/plperl/plperl.c --- b/src/pl/plperl/plperl.c *************** compile_plperl_function(Oid fn_oid, bool *** 2777,2785 **** /************************************************************ * Allocate a context that will hold all PG data for the procedure. ************************************************************/ ! proc_cxt = AllocSetContextCreate(TopMemoryContext, ! NameStr(procStruct->proname), ! ALLOCSET_SMALL_SIZES); /************************************************************ * Allocate and fill a new procedure description block. --- 2777,2786 ---- /************************************************************ * Allocate a context that will hold all PG data for the procedure. ************************************************************/ ! proc_cxt = AllocSetContextCreateExtended(TopMemoryContext, ! NameStr(procStruct->proname), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); /************************************************************ * Allocate and fill a new procedure description block. diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c index b7c24e3..990a33c 100644 *** a/src/pl/plpython/plpy_procedure.c --- b/src/pl/plpython/plpy_procedure.c *************** PLy_procedure_create(HeapTuple procTup, *** 166,174 **** } /* Create long-lived context that all procedure info will live in */ ! cxt = AllocSetContextCreate(TopMemoryContext, ! procName, ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(cxt); --- 166,175 ---- } /* Create long-lived context that all procedure info will live in */ ! cxt = AllocSetContextCreateExtended(TopMemoryContext, ! procName, ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(cxt); diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index e0792d9..8069784 100644 *** a/src/pl/tcl/pltcl.c --- b/src/pl/tcl/pltcl.c *************** compile_pltcl_function(Oid fn_oid, Oid t *** 1471,1479 **** * 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); /************************************************************ * Allocate and fill a new procedure description block. --- 1471,1480 ---- * Allocate a context that will hold all PG data for the procedure. * We use the internal proc name as the context name. ************************************************************/ ! proc_cxt = AllocSetContextCreateExtended(TopMemoryContext, ! internal_proname, ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); /************************************************************ * Allocate and fill a new procedure description block.
On Tue, Dec 12, 2017 at 2:30 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > 379 CurTransactionContext was never used > 24 CurTransactionContext was used > 66978 ExprContext was never used > 17364 ExprContext was used > 11139 SPI Exec was never used > 2421 SPI Exec was used > 38386 ginPlaceToPage temporary context was never used > 3348 ginPlaceToPage temporary context was used It strikes me that a way to optimize these cases even more would be to postpone creating the context until it's actually needed. That might not always be a reasonable plan -- in particular, it occurs to me to think that CurTransactionContext is probably so widely used that changing anything about how it works would probably be really painful -- but it might be possible in some cases. Another idea I have is that perhaps we could arrange to reuse contexts instead of destroying them and recreating them. For example, when asked to delete a context, we could instead push it on a linked list of old contexts, or only if the list isn't too long already, and when asked to create one, we could pop from the list. Or we could keep around an array of, say, 1024 contexts that are never freed and only allocated dynamically when we run out. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
On 2017-12-12 14:50:37 -0500, Robert Haas wrote: > On Tue, Dec 12, 2017 at 2:30 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > > 379 CurTransactionContext was never used > > 24 CurTransactionContext was used > > 66978 ExprContext was never used > > 17364 ExprContext was used > > 11139 SPI Exec was never used > > 2421 SPI Exec was used > > 38386 ginPlaceToPage temporary context was never used > > 3348 ginPlaceToPage temporary context was used > > It strikes me that a way to optimize these cases even more would be to > postpone creating the context until it's actually needed. That might > not always be a reasonable plan -- in particular, it occurs to me to > think that CurTransactionContext is probably so widely used that > changing anything about how it works would probably be really painful > -- but it might be possible in some cases. That's not generally easy without slowing things down though - e.g. we don't want to check for ExprContext's existence before every use, there can be billions of usages in a single analytics query. The branches (yea yea ;)) would show up as being noticeable. There are a few places where could probably reliably *detect* that they're not needed however. E.g. plenty executor nodes only need a ExprContext when either a qual or projection is needed, both are pretty cheap to detect at ExecInitNode() time. > Another idea I have is that perhaps we could arrange to reuse contexts > instead of destroying them and recreating them. For example, when > asked to delete a context, we could instead push it on a linked list > of old contexts, or only if the list isn't too long already, and when > asked to create one, we could pop from the list. Or we could keep > around an array of, say, 1024 contexts that are never freed and only > allocated dynamically when we run out. I'm a bit doubtful that's going to help, maintaining that list isn't going to be free, and the lifetime and number of those contexts aren't always going to match up. I think you're somewhat on to something however: I do think that especially executor startup would have a good chance of avoiding noticeable overhead by batching the allocation of all these tiny contexts together - I just don't quite know how given how our current initialization works. The best thing to do would probably to do two walks during executor initialization, one to compute memory sizes, and a second to initialize pre-requested memory that's laid out serially... But obviously that's not a small change. Greetings, Andres Freund
Andres Freund <andres@anarazel.de> writes: > On 2017-12-12 14:50:37 -0500, Robert Haas wrote: >> It strikes me that a way to optimize these cases even more would be to >> postpone creating the context until it's actually needed. That might >> not always be a reasonable plan -- in particular, it occurs to me to >> think that CurTransactionContext is probably so widely used that >> changing anything about how it works would probably be really painful >> -- but it might be possible in some cases. > That's not generally easy without slowing things down though - e.g. we > don't want to check for ExprContext's existence before every use, there > can be billions of usages in a single analytics query. The branches (yea > yea ;)) would show up as being noticeable. Yeah. Also, in most of these cases what we're doing with the context is installing it as CurrentMemoryContext while we execute some random code that might or might not need to palloc anything. We can't just set CurrentMemoryContext to null - for one thing, that would leave no trace of how the context should get set up if it's needed. You could imagine installing some sort of placeholder, but really that's the mechanism we've already got, in the case where we just make a context header and no blocks. >> Another idea I have is that perhaps we could arrange to reuse contexts >> instead of destroying them and recreating them. > I'm a bit doubtful that's going to help, maintaining that list isn't > going to be free, and the lifetime and number of those contexts aren't > always going to match up. Actually, this seems like a really promising idea to me. To the extent that an empty context has standard parameters, they're all interchangeable, so you could imagine that AllocSetDelete shoves it onto a freelist after resetting it, instead of just giving it back to libc. I'm slightly worried about creating allocation islands that way, but that problem is probably surmountable, if it's real at all. However, that seems like a different patch from what I'm working on here. regards, tom lane
I wrote: > Andres Freund <andres@anarazel.de> writes: >> On 2017-12-12 14:50:37 -0500, Robert Haas wrote: >>> Another idea I have is that perhaps we could arrange to reuse contexts >>> instead of destroying them and recreating them. >> I'm a bit doubtful that's going to help, maintaining that list isn't >> going to be free, and the lifetime and number of those contexts aren't >> always going to match up. > Actually, this seems like a really promising idea to me. To the extent > that an empty context has standard parameters, they're all > interchangeable, so you could imagine that AllocSetDelete shoves it > onto a freelist after resetting it, instead of just giving it back to > libc. I'm slightly worried about creating allocation islands that > way, but that problem is probably surmountable, if it's real at all. > However, that seems like a different patch from what I'm working on > here. Oh, wait a minute. If we think of going that direction, then actually what I did in v3 is counterproductive: we're better off making contexts as much alike as possible, so that they can be recycled more easily. So the attached goes back to v2's design where all aset.c contexts are allocated together with their keeper block, and instead provides context freelists more or less per Robert's suggestion. This proves to be a bit faster than v3 on all cases I tried, and is likely to be considerably better on cases that don't match v3's expectations about which contexts will do what. The details of the freelist management are ripe for bikeshedding, no doubt. I set it up so that there are separate freelists for "default" and "small" contexts, and we allow up to 100 contexts per freelist (so max space wastage of less than 1MB). If a freelist gets too full, we simply release everything in it back to libc, then add the context we were about to add. The thought here is that a query that made 101 transient contexts probably did not stop at 102; more likely, a bunch of additional deletions are coming, so there's no value in sweating over exactly which contexts to keep. Flushing all the previously-deleted contexts should help to reduce the process memory map in the typical case where a lot of contexts get released in roughly reverse order of allocation. There might be a better way to do that --- I toyed with trying to release contexts with higher memory addresses, for example. But I'm not sure it's worth spending a lot of time on, unless someone can show a pathological case for this way. I am pretty happy with this now and think it could be committed. If anyone wants to research the freelist management issue some more, they're welcome to, but I don't think that question needs to hold up committing the basic improvement. (The hardest part would likely be finding suitable test cases, anyway. Most queries don't get anywhere near using 100 transient contexts.) BTW, while I didn't do it here, I am very strongly tempted to replace the test-and-elog validation of context size parameters with Asserts. When we originally designed this code we thought people might be calculating those parameters on the fly, but in nigh twenty years nobody has ever done so, so the runtime cost doesn't really seem worth it. I got some results while I was rearranging code that suggest that skipping those tests could be worthwhile in practice, now that there's a common short path through AllocSetContextCreate. regards, tom lane diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c index 868c14e..adbbc44 100644 *** a/contrib/amcheck/verify_nbtree.c --- b/contrib/amcheck/verify_nbtree.c *************** bt_check_every_level(Relation rel, bool *** 295,303 **** /* Create context for page */ state->targetcontext = AllocSetContextCreate(CurrentMemoryContext, "amcheck context", ! ALLOCSET_DEFAULT_MINSIZE, ! ALLOCSET_DEFAULT_INITSIZE, ! ALLOCSET_DEFAULT_MAXSIZE); state->checkstrategy = GetAccessStrategy(BAS_BULKREAD); /* Get true root block from meta-page */ --- 295,301 ---- /* Create context for page */ state->targetcontext = AllocSetContextCreate(CurrentMemoryContext, "amcheck context", ! ALLOCSET_DEFAULT_SIZES); state->checkstrategy = GetAccessStrategy(BAS_BULKREAD); /* Get true root block from meta-page */ diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 046898c..e93d740 100644 *** a/src/backend/access/transam/xact.c --- b/src/backend/access/transam/xact.c *************** AtStart_Memory(void) *** 997,1007 **** */ if (TransactionAbortContext == NULL) TransactionAbortContext = ! AllocSetContextCreate(TopMemoryContext, ! "TransactionAbortContext", ! 32 * 1024, ! 32 * 1024, ! 32 * 1024); /* * We shouldn't have a transaction context already. --- 997,1008 ---- */ if (TransactionAbortContext == NULL) TransactionAbortContext = ! AllocSetContextCreateExtended(TopMemoryContext, ! "TransactionAbortContext", ! 0, ! 32 * 1024, ! 32 * 1024, ! 32 * 1024); /* * We shouldn't have a transaction context already. diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index ef156e4..5c4018e 100644 *** a/src/backend/catalog/partition.c --- b/src/backend/catalog/partition.c *************** RelationBuildPartitionDesc(Relation rel) *** 520,528 **** } /* Now build the actual relcache partition descriptor */ ! rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(rel), ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt); result = (PartitionDescData *) palloc0(sizeof(PartitionDescData)); --- 520,529 ---- } /* Now build the actual relcache partition descriptor */ ! rel->rd_pdcxt = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(rel), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt); result = (PartitionDescData *) palloc0(sizeof(PartitionDescData)); diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 086a6ef..a7f426d 100644 *** a/src/backend/commands/subscriptioncmds.c --- b/src/backend/commands/subscriptioncmds.c *************** publicationListToArray(List *publist) *** 259,267 **** /* Create memory context for temporary allocations. */ memcxt = AllocSetContextCreate(CurrentMemoryContext, "publicationListToArray to array", ! ALLOCSET_DEFAULT_MINSIZE, ! ALLOCSET_DEFAULT_INITSIZE, ! ALLOCSET_DEFAULT_MAXSIZE); oldcxt = MemoryContextSwitchTo(memcxt); datums = (Datum *) palloc(sizeof(Datum) * list_length(publist)); --- 259,265 ---- /* Create memory context for temporary allocations. */ memcxt = AllocSetContextCreate(CurrentMemoryContext, "publicationListToArray to array", ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(memcxt); datums = (Datum *) palloc(sizeof(Datum) * list_length(publist)); diff --git a/src/backend/lib/knapsack.c b/src/backend/lib/knapsack.c index ddf2b9a..490c0cc 100644 *** a/src/backend/lib/knapsack.c --- b/src/backend/lib/knapsack.c *************** DiscreteKnapsack(int max_weight, int num *** 57,65 **** { MemoryContext local_ctx = AllocSetContextCreate(CurrentMemoryContext, "Knapsack", ! ALLOCSET_SMALL_MINSIZE, ! ALLOCSET_SMALL_INITSIZE, ! ALLOCSET_SMALL_MAXSIZE); MemoryContext oldctx = MemoryContextSwitchTo(local_ctx); double *values; Bitmapset **sets; --- 57,63 ---- { MemoryContext local_ctx = AllocSetContextCreate(CurrentMemoryContext, "Knapsack", ! ALLOCSET_SMALL_SIZES); MemoryContext oldctx = MemoryContextSwitchTo(local_ctx); double *values; Bitmapset **sets; diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c index a613ef4..24be3ce 100644 *** a/src/backend/replication/logical/launcher.c --- b/src/backend/replication/logical/launcher.c *************** ApplyLauncherMain(Datum main_arg) *** 925,933 **** /* 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); oldctx = MemoryContextSwitchTo(subctx); /* search for subscriptions to start or stop. */ --- 925,931 ---- /* Use temporary context for the database list and worker info. */ subctx = AllocSetContextCreate(TopMemoryContext, "Logical Replication Launcher sublist", ! ALLOCSET_DEFAULT_SIZES); oldctx = MemoryContextSwitchTo(subctx); /* search for subscriptions to start or stop. */ diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index fa95bab..5ac391d 100644 *** a/src/backend/replication/logical/reorderbuffer.c --- b/src/backend/replication/logical/reorderbuffer.c *************** ReorderBufferAllocate(void) *** 237,252 **** --- 237,255 ---- 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); diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index c312654..550b156 100644 *** a/src/backend/replication/pgoutput/pgoutput.c --- b/src/backend/replication/pgoutput/pgoutput.c *************** pgoutput_startup(LogicalDecodingContext *** 152,160 **** /* 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); ctx->output_plugin_private = data; --- 152,158 ---- /* Create our memory context for private allocations. */ data->context = AllocSetContextCreate(ctx->context, "logical replication output context", ! ALLOCSET_DEFAULT_SIZES); ctx->output_plugin_private = data; diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 12a5f15..3a9233e 100644 *** a/src/backend/utils/cache/relcache.c --- b/src/backend/utils/cache/relcache.c *************** RelationBuildRuleLock(Relation relation) *** 669,677 **** /* * Make the private context. Assume it'll not contain much data. */ ! rulescxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(relation), ! ALLOCSET_SMALL_SIZES); relation->rd_rulescxt = rulescxt; /* --- 669,678 ---- /* * Make the private context. Assume it'll not contain much data. */ ! rulescxt = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(relation), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); relation->rd_rulescxt = rulescxt; /* *************** RelationBuildPartitionKey(Relation relat *** 984,992 **** ReleaseSysCache(tuple); /* Success --- now copy to the cache memory */ ! partkeycxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(relation), ! ALLOCSET_SMALL_SIZES); relation->rd_partkeycxt = partkeycxt; oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt); relation->rd_partkey = copy_partition_key(key); --- 985,994 ---- ReleaseSysCache(tuple); /* Success --- now copy to the cache memory */ ! 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); *************** RelationInitIndexAccessInfo(Relation rel *** 1566,1574 **** * 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); relation->rd_indexcxt = indexcxt; /* --- 1568,1577 ---- * 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 = AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(relation), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); relation->rd_indexcxt = indexcxt; /* *************** load_relcache_init_file(bool shared) *** 5537,5545 **** * prepare index info context --- parameters should match * RelationInitIndexAccessInfo */ ! indexcxt = AllocSetContextCreate(CacheMemoryContext, ! RelationGetRelationName(rel), ! ALLOCSET_SMALL_SIZES); rel->rd_indexcxt = indexcxt; /* --- 5540,5550 ---- * prepare index info context --- parameters should match * RelationInitIndexAccessInfo */ ! indexcxt = ! AllocSetContextCreateExtended(CacheMemoryContext, ! RelationGetRelationName(rel), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); rel->rd_indexcxt = indexcxt; /* diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c index da5c8ea..29cf93a 100644 *** a/src/backend/utils/cache/ts_cache.c --- b/src/backend/utils/cache/ts_cache.c *************** lookup_ts_dictionary_cache(Oid dictId) *** 294,302 **** 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); } else { --- 294,303 ---- Assert(!found); /* it wasn't there a moment ago */ /* Create private memory context the first time through */ ! saveCtx = AllocSetContextCreateExtended(CacheMemoryContext, ! NameStr(dict->dictname), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); } else { diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index 71f5f06..c88efc3 100644 *** a/src/backend/utils/hash/dynahash.c --- b/src/backend/utils/hash/dynahash.c *************** hash_create(const char *tabname, long ne *** 340,348 **** CurrentDynaHashCxt = info->hcxt; else CurrentDynaHashCxt = TopMemoryContext; ! CurrentDynaHashCxt = AllocSetContextCreate(CurrentDynaHashCxt, ! tabname, ! ALLOCSET_DEFAULT_SIZES); } /* Initialize the hash header, plus a copy of the table name */ --- 340,350 ---- CurrentDynaHashCxt = info->hcxt; else CurrentDynaHashCxt = TopMemoryContext; ! CurrentDynaHashCxt = ! AllocSetContextCreateExtended(CurrentDynaHashCxt, ! tabname, ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_DEFAULT_SIZES); } /* Initialize the hash header, plus a copy of the table name */ diff --git a/src/backend/utils/mmgr/README b/src/backend/utils/mmgr/README index 296fa19..a42e568 100644 *** a/src/backend/utils/mmgr/README --- b/src/backend/utils/mmgr/README *************** every other context is a direct or indir *** 177,184 **** 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 allocating stuff here unless really necessary, and especially avoid running with CurrentMemoryContext pointing here. --- 177,183 ---- 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. Avoid allocating stuff here unless really necessary, and especially avoid running with CurrentMemoryContext pointing here. *************** a maximum block size. Selecting smaller *** 420,430 **** 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. 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 --- 419,428 ---- 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, 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 diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 1bd1c34..4a42d1b 100644 *** a/src/backend/utils/mmgr/aset.c --- b/src/backend/utils/mmgr/aset.c *************** *** 93,98 **** --- 93,101 ---- * * 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. *-------------------- */ *************** typedef void *AllocPointer; *** 113,119 **** * * 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 * being logically empty, because we don't attempt to detect pfree'ing the * last active chunk. */ --- 116,122 ---- * * 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 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. */ *************** typedef struct AllocSetContext *** 127,134 **** Size initBlockSize; /* initial block size */ Size maxBlockSize; /* maximum block size */ Size nextBlockSize; /* next block size to allocate */ Size allocChunkLimit; /* effective chunk size limit */ ! AllocBlock keeper; /* if not NULL, keep this block over resets */ } AllocSetContext; typedef AllocSetContext *AllocSet; --- 130,140 ---- 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; /* 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; *************** typedef struct AllocChunkData *** 216,227 **** ((AllocPointer)(((char *)(chk)) + ALLOC_CHUNKHDRSZ)) /* * 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); --- 222,277 ---- ((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-allocated + * 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 AllocSetReset(MemoryContext context); static void AllocSetDelete(MemoryContext context); static Size AllocSetGetChunkSpace(MemoryContext context, void *pointer); *************** static void AllocSetCheck(MemoryContext *** 236,246 **** /* * This is the virtual function table for AllocSet contexts. */ ! static MemoryContextMethods AllocSetMethods = { AllocSetAlloc, AllocSetFree, AllocSetRealloc, - AllocSetInit, AllocSetReset, AllocSetDelete, AllocSetGetChunkSpace, --- 286,295 ---- /* * This is the virtual function table for AllocSet contexts. */ ! static const MemoryContextMethods AllocSetMethods = { AllocSetAlloc, AllocSetFree, AllocSetRealloc, AllocSetReset, AllocSetDelete, AllocSetGetChunkSpace, *************** AllocSetFreeIndex(Size size) *** 325,351 **** /* ! * AllocSetContextCreate * 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) * 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. * Most callers should abstract the context size parameters using a macro ! * such as ALLOCSET_DEFAULT_SIZES. */ MemoryContext ! AllocSetContextCreate(MemoryContext parent, ! const char *name, ! Size minContextSize, ! Size initBlockSize, ! Size maxBlockSize) { AllocSet set; /* Assert we padded AllocChunkData properly */ StaticAssertStmt(ALLOC_CHUNKHDRSZ == MAXALIGN(ALLOC_CHUNKHDRSZ), --- 374,408 ---- /* ! * 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: 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. (This is now *required* when going ! * through the AllocSetContextCreate macro.) */ MemoryContext ! 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), *************** AllocSetContextCreate(MemoryContext pare *** 355,390 **** "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. */ - 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 */ set->initBlockSize = initBlockSize; set->maxBlockSize = maxBlockSize; set->nextBlockSize = initBlockSize; /* * Compute the allocation chunk size limit for this context. It can't be --- 412,546 ---- "padding calculation in AllocChunkData is wrong"); /* ! * First, validate maxBlockSize, so that we don't accept a bogus value ! * when taking the allocate-from-freelist path. (If the other parameters ! * match a freelist, they must be OK.) */ if (maxBlockSize != MAXALIGN(maxBlockSize) || maxBlockSize < initBlockSize || !AllocHugeSizeIsValid(maxBlockSize)) /* must be safe to double */ elog(ERROR, "invalid maxBlockSize for memory context: %zu", maxBlockSize); + + /* + * Check whether the remaining parameters match either available freelist. + */ + 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; + } + } + + /* + * Validate the remaining 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. + */ + if (initBlockSize != MAXALIGN(initBlockSize) || + initBlockSize < 1024) + elog(ERROR, "invalid initBlockSize for memory context: %zu", + initBlockSize); if (minContextSize != 0 && (minContextSize != MAXALIGN(minContextSize) || ! minContextSize < 1024 || ! minContextSize > maxBlockSize)) elog(ERROR, "invalid minContextSize for memory context: %zu", minContextSize); ! /* 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 *************** AllocSetContextCreate(MemoryContext pare *** 410,483 **** (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); ! } 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. * * 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. */ static void AllocSetReset(MemoryContext context) --- 566,595 ---- (Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION)) set->allocChunkLimit >>= 1; ! /* Finally, do the type-independent part of context creation */ ! MemoryContextCreate((MemoryContext) set, ! T_AllocSetContext, ! headerSize, ! sizeof(AllocSetContext), ! &AllocSetMethods, ! parent, ! name, ! flags); return (MemoryContext) set; } /* * AllocSetReset * Frees all memory which is allocated in the given set. * * 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 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) *************** AllocSetReset(MemoryContext context) *** 497,503 **** block = set->blocks; ! /* New blocks list is either empty or just the keeper block */ set->blocks = set->keeper; while (block != NULL) --- 609,615 ---- block = set->blocks; ! /* New blocks list will be just the keeper block */ set->blocks = set->keeper; while (block != NULL) *************** AllocSetReset(MemoryContext context) *** 540,546 **** * 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) --- 652,657 ---- *************** AllocSetDelete(MemoryContext context) *** 555,565 **** AllocSetCheck(context); #endif ! /* Make it look empty, just in case... */ ! MemSetAligned(set->freelist, 0, sizeof(set->freelist)); ! set->blocks = NULL; ! set->keeper = NULL; while (block != NULL) { AllocBlock next = block->next; --- 666,713 ---- AllocSetCheck(context); #endif ! /* ! * 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 there is to get rid of is the context header */ + 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; *************** AllocSetDelete(MemoryContext context) *** 567,575 **** #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif ! free(block); block = next; } } /* --- 715,729 ---- #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif ! ! if (block != set->keeper) ! free(block); ! block = next; } + + /* Finally, free the context header, including the keeper block */ + free(set); } /* *************** AllocSetAlloc(MemoryContext context, Siz *** 807,824 **** 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); --- 961,966 ---- *************** AllocSetStats(MemoryContext context, int *** 1205,1215 **** AllocSet set = (AllocSet) context; Size nblocks = 0; Size freechunks = 0; ! Size totalspace = 0; Size freespace = 0; AllocBlock block; int fidx; for (block = set->blocks; block != NULL; block = block->next) { nblocks++; --- 1347,1360 ---- AllocSet set = (AllocSet) context; Size nblocks = 0; Size freechunks = 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++; *************** static void *** 1264,1270 **** AllocSetCheck(MemoryContext context) { AllocSet set = (AllocSet) context; ! char *name = set->header.name; AllocBlock prevblock; AllocBlock block; --- 1409,1415 ---- AllocSetCheck(MemoryContext context) { AllocSet set = (AllocSet) context; ! const char *name = set->header.name; AllocBlock prevblock; AllocBlock block; diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c index 19390fa..10d0fc1 100644 *** a/src/backend/utils/mmgr/generation.c --- b/src/backend/utils/mmgr/generation.c *************** typedef struct GenerationContext *** 61,66 **** --- 61,67 ---- /* 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 */ *************** struct GenerationChunk *** 149,155 **** 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); --- 150,155 ---- *************** static void GenerationCheck(MemoryContex *** 164,174 **** /* * This is the virtual function table for Generation contexts. */ ! static MemoryContextMethods GenerationMethods = { GenerationAlloc, GenerationFree, GenerationRealloc, - GenerationInit, GenerationReset, GenerationDelete, GenerationGetChunkSpace, --- 164,173 ---- /* * This is the virtual function table for Generation contexts. */ ! static const MemoryContextMethods GenerationMethods = { GenerationAlloc, GenerationFree, GenerationRealloc, GenerationReset, GenerationDelete, GenerationGetChunkSpace, *************** static MemoryContextMethods GenerationMe *** 208,215 **** --- 207,216 ---- MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, + int flags, Size blockSize) { + Size headerSize; GenerationContext *set; /* Assert we padded GenerationChunk properly */ *************** GenerationContextCreate(MemoryContext pa *** 231,259 **** 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); ! set->blockSize = blockSize; ! return (MemoryContext) set; ! } ! /* ! * GenerationInit ! * Context-type-specific initialization routine. ! */ ! static void ! GenerationInit(MemoryContext context) ! { ! GenerationContext *set = (GenerationContext *) context; set->block = NULL; dlist_init(&set->blocks); } /* --- 232,282 ---- elog(ERROR, "invalid blockSize for memory context: %zu", blockSize); ! /* ! * 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. ! */ ! /* 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)); ! 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))); ! } ! /* ! * 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; } /* *************** GenerationReset(MemoryContext context) *** 296,311 **** /* * 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. */ static void GenerationDelete(MemoryContext context) { ! /* just reset to release all the GenerationBlocks */ GenerationReset(context); ! /* we are not responsible for deleting the context node itself */ } /* --- 319,333 ---- /* * GenerationDelete ! * Free all memory which is allocated in the given context. */ static void GenerationDelete(MemoryContext context) { ! /* Reset to release all the GenerationBlocks */ GenerationReset(context); ! /* And free the context header */ ! free(context); } /* *************** GenerationIsEmpty(MemoryContext context) *** 659,665 **** /* * GenerationStats ! * Compute stats about memory consumption of an Generation. * * level: recursion level (0 at top level); used for print indentation. * print: true to print stats to stderr. --- 681,687 ---- /* * GenerationStats ! * 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. *************** GenerationStats(MemoryContext context, i *** 676,685 **** Size nblocks = 0; Size nchunks = 0; Size nfreechunks = 0; ! Size totalspace = 0; Size freespace = 0; dlist_iter iter; dlist_foreach(iter, &set->blocks) { GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); --- 698,710 ---- Size nblocks = 0; Size nchunks = 0; Size nfreechunks = 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); *************** static void *** 727,733 **** GenerationCheck(MemoryContext context) { GenerationContext *gen = (GenerationContext *) context; ! char *name = context->name; dlist_iter iter; /* walk all blocks in this context */ --- 752,758 ---- GenerationCheck(MemoryContext context) { GenerationContext *gen = (GenerationContext *) context; ! const char *name = context->name; dlist_iter iter; /* walk all blocks in this context */ diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index c5c311f..97382a6 100644 *** a/src/backend/utils/mmgr/mcxt.c --- b/src/backend/utils/mmgr/mcxt.c *************** MemoryContextInit(void) *** 91,99 **** 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.) */ TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", --- 91,97 ---- AssertState(TopMemoryContext == NULL); /* ! * First, initialize TopMemoryContext, which is the parent of all others. */ TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", *************** MemoryContextInit(void) *** 118,128 **** * 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); MemoryContextAllowInCriticalSection(ErrorContext, true); } --- 116,127 ---- * This should be the last step in this function, as elog.c assumes memory * management works once ErrorContext is non-null. */ ! ErrorContext = AllocSetContextCreateExtended(TopMemoryContext, ! "ErrorContext", ! 0, ! 8 * 1024, ! 8 * 1024, ! 8 * 1024); MemoryContextAllowInCriticalSection(ErrorContext, true); } *************** MemoryContextResetChildren(MemoryContext *** 191,200 **** * 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. */ void MemoryContextDelete(MemoryContext context) --- 190,198 ---- * Delete a context and its descendants, and release all space * allocated therein. * ! * 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) *************** MemoryContextDelete(MemoryContext contex *** 205,211 **** /* And not CurrentMemoryContext, either */ Assert(context != CurrentMemoryContext); ! MemoryContextDeleteChildren(context); /* * It's not entirely clear whether 'tis better to do this before or after --- 203,211 ---- /* And not CurrentMemoryContext, either */ Assert(context != CurrentMemoryContext); ! /* 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 *************** MemoryContextDelete(MemoryContext contex *** 223,230 **** MemoryContextSetParent(context, NULL); context->methods->delete_context(context); VALGRIND_DESTROY_MEMPOOL(context); - pfree(context); } /* --- 223,230 ---- MemoryContextSetParent(context, NULL); context->methods->delete_context(context); + VALGRIND_DESTROY_MEMPOOL(context); } /* *************** MemoryContextContains(MemoryContext cont *** 587,686 **** 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 * 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. * ! * Note that the name field of a MemoryContext does not point to ! * separately-allocated storage, so it should not be freed at context ! * deletion. ! *-------------------- */ ! MemoryContext ! MemoryContextCreate(NodeTag tag, Size size, ! MemoryContextMethods *methods, MemoryContext parent, ! const char *name) { ! MemoryContext node; ! Size needed = size + strlen(name) + 1; ! ! /* 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); ! } ! /* Initialize the node as best we can */ ! MemSet(node, 0, size); node->type = tag; node->methods = methods; ! node->parent = NULL; /* for the moment */ node->firstchild = NULL; node->prevchild = NULL; ! node->nextchild = NULL; ! node->isReset = true; ! node->name = ((char *) node) + size; ! strcpy(node->name, name); ! /* Type-specific routine finishes any other essential initialization */ ! node->methods->init(node); ! /* OK to link node to parent (if any) */ ! /* Could use MemoryContextSetParent here, but doesn't seem worthwhile */ if (parent) { - node->parent = parent; node->nextchild = parent->firstchild; if (parent->firstchild != NULL) parent->firstchild->prevchild = node; --- 587,671 ---- 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 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. * ! * 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. * ! * Context routines generally assume that MemoryContextCreate can't fail, ! * so this can contain Assert but not elog/ereport. */ ! void ! MemoryContextCreate(MemoryContext node, ! NodeTag tag, Size size, Size nameoffset, ! const MemoryContextMethods *methods, MemoryContext parent, ! const char *name, ! int flags) { ! /* Creating new memory contexts is not allowed in a critical section */ Assert(CritSectionCount == 0); ! /* Check size is sane */ ! Assert(nameoffset >= sizeof(MemoryContextData)); ! Assert((flags & MEMCONTEXT_COPY_NAME) ? ! size >= nameoffset + strlen(name) + 1 : ! size >= nameoffset); ! /* Initialize all standard fields of memory context header */ node->type = tag; + node->isReset = true; node->methods = methods; ! node->parent = parent; node->firstchild = NULL; node->prevchild = NULL; ! node->reset_cbs = NULL; ! if (flags & MEMCONTEXT_COPY_NAME) ! { ! /* Insert context name into space reserved for it */ ! char *namecopy = ((char *) node) + nameoffset; ! 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->nextchild = parent->firstchild; if (parent->firstchild != NULL) parent->firstchild->prevchild = node; *************** MemoryContextCreate(NodeTag tag, Size si *** 688,698 **** /* inherit allowInCritSection flag from parent */ node->allowInCritSection = parent->allowInCritSection; } VALGRIND_CREATE_MEMPOOL(node, 0, false); - - /* Return to type-specific creation routine to finish up */ - return node; } /* --- 673,685 ---- /* inherit allowInCritSection flag from parent */ node->allowInCritSection = parent->allowInCritSection; } + else + { + node->nextchild = NULL; + node->allowInCritSection = false; + } VALGRIND_CREATE_MEMPOOL(node, 0, false); } /* diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c index ee21752..42c0882 100644 *** a/src/backend/utils/mmgr/slab.c --- b/src/backend/utils/mmgr/slab.c *************** typedef struct SlabContext *** 67,72 **** --- 67,73 ---- 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 */ *************** typedef struct SlabChunk *** 126,132 **** 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); --- 127,132 ---- *************** static void SlabCheck(MemoryContext cont *** 140,150 **** /* * This is the virtual function table for Slab contexts. */ ! static MemoryContextMethods SlabMethods = { SlabAlloc, SlabFree, SlabRealloc, - SlabInit, SlabReset, SlabDelete, SlabGetChunkSpace, --- 140,149 ---- /* * This is the virtual function table for Slab contexts. */ ! static const MemoryContextMethods SlabMethods = { SlabAlloc, SlabFree, SlabRealloc, SlabReset, SlabDelete, SlabGetChunkSpace, *************** static MemoryContextMethods SlabMethods *** 177,200 **** * Create a new Slab context. * * parent: parent context, or NULL if top-level context ! * name: name of context (for debugging --- string will be copied) * blockSize: allocation block size * chunkSize: allocation chunk size * * The chunkSize may not exceed: * MAXALIGN_DOWN(SIZE_MAX) - MAXALIGN(sizeof(SlabBlock)) - SLAB_CHUNKHDRSZ - * */ MemoryContext SlabContextCreate(MemoryContext parent, const char *name, Size blockSize, Size chunkSize) { int chunksPerBlock; Size fullChunkSize; Size freelistSize; SlabContext *slab; /* Assert we padded SlabChunk properly */ StaticAssertStmt(sizeof(SlabChunk) == MAXALIGN(sizeof(SlabChunk)), --- 176,205 ---- * Create a new Slab 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 * 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)), *************** SlabContextCreate(MemoryContext parent, *** 227,265 **** /* make sure the chunks actually fit on the block */ Assert((fullChunkSize * chunksPerBlock) + sizeof(SlabBlock) <= blockSize); ! /* Do the type-independent part of context creation */ ! slab = (SlabContext *) ! MemoryContextCreate(T_SlabContext, ! (offsetof(SlabContext, freelist) + freelistSize), ! &SlabMethods, ! parent, ! name); ! slab->blockSize = blockSize; slab->chunkSize = chunkSize; slab->fullChunkSize = fullChunkSize; 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); /* initialize the freelist slots */ for (i = 0; i < (slab->chunksPerBlock + 1); i++) dlist_init(&slab->freelist[i]); } /* --- 232,289 ---- /* make sure the chunks actually fit on the block */ Assert((fullChunkSize * chunksPerBlock) + sizeof(SlabBlock) <= blockSize); ! /* ! * Allocate the context header. Unlike aset.c, we never try to combine ! * this with the first regular block; not worth the extra complication. ! */ ! /* 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; ! ! 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))); ! } ! ! /* ! * 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->minFreeChunks = 0; ! 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; } /* *************** SlabReset(MemoryContext context) *** 308,321 **** /* * SlabDelete ! * Frees all memory which is allocated in the given slab, in preparation ! * for deletion of the slab. We simply call SlabReset(). */ static void SlabDelete(MemoryContext context) { ! /* just reset the context */ SlabReset(context); } /* --- 332,346 ---- /* * SlabDelete ! * Free all memory which is allocated in the given context. */ static void SlabDelete(MemoryContext context) { ! /* Reset to release all the SlabBlocks */ SlabReset(context); + /* And free the context header */ + free(context); } /* *************** SlabIsEmpty(MemoryContext context) *** 613,619 **** /* * SlabStats ! * Compute stats about memory consumption of an Slab. * * level: recursion level (0 at top level); used for print indentation. * print: true to print stats to stderr. --- 638,644 ---- /* * SlabStats ! * 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. *************** SlabStats(MemoryContext context, int lev *** 626,636 **** SlabContext *slab = castNode(SlabContext, context); Size nblocks = 0; Size freechunks = 0; ! Size totalspace = 0; Size freespace = 0; int i; ! Assert(slab); for (i = 0; i <= slab->chunksPerBlock; i++) { --- 651,662 ---- SlabContext *slab = castNode(SlabContext, context); Size nblocks = 0; Size freechunks = 0; ! Size totalspace; Size freespace = 0; int i; ! /* Include context header in totalspace */ ! totalspace = slab->headerSize; for (i = 0; i <= slab->chunksPerBlock; i++) { *************** SlabCheck(MemoryContext context) *** 682,688 **** { int i; SlabContext *slab = castNode(SlabContext, context); ! char *name = slab->header.name; char *freechunks; Assert(slab); --- 708,714 ---- { int i; SlabContext *slab = castNode(SlabContext, context); ! const char *name = slab->header.name; char *freechunks; Assert(slab); diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h index e22d9fb..c7eb1e7 100644 *** a/src/include/nodes/memnodes.h --- b/src/include/nodes/memnodes.h *************** typedef struct MemoryContextMethods *** 57,63 **** /* 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); --- 57,62 ---- *************** typedef struct MemoryContextData *** 76,87 **** /* 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 */ 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) */ MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */ } MemoryContextData; --- 75,86 ---- /* 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 */ ! 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 */ ! const char *name; /* context name (just for debugging) */ MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */ } MemoryContextData; diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index d177b0c..9c30eb7 100644 *** a/src/include/utils/memutils.h --- b/src/include/utils/memutils.h *************** GetMemoryChunkContext(void *pointer) *** 132,141 **** * 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, MemoryContext parent, ! const char *name); /* --- 132,143 ---- * context creation. It's intended to be called from context-type- * specific creation routines, and noplace else. */ ! extern void MemoryContextCreate(MemoryContext node, ! NodeTag tag, Size size, Size nameoffset, ! const MemoryContextMethods *methods, MemoryContext parent, ! const char *name, ! int flags); /* *************** extern MemoryContext MemoryContextCreate *** 143,166 **** */ /* aset.c */ ! extern MemoryContext AllocSetContextCreate(MemoryContext parent, ! const char *name, ! Size minContextSize, ! Size initBlockSize, ! Size maxBlockSize); /* slab.c */ extern MemoryContext SlabContextCreate(MemoryContext parent, const char *name, Size blockSize, Size chunkSize); /* generation.c */ extern MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, Size blockSize); /* * Recommended default alloc parameters, suitable for "ordinary" contexts * that might hold quite a lot of data. */ --- 145,193 ---- */ /* aset.c */ ! 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. */ diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 9f53132..41fd0ba 100644 *** a/src/pl/plperl/plperl.c --- b/src/pl/plperl/plperl.c *************** compile_plperl_function(Oid fn_oid, bool *** 2777,2785 **** /************************************************************ * Allocate a context that will hold all PG data for the procedure. ************************************************************/ ! proc_cxt = AllocSetContextCreate(TopMemoryContext, ! NameStr(procStruct->proname), ! ALLOCSET_SMALL_SIZES); /************************************************************ * Allocate and fill a new procedure description block. --- 2777,2786 ---- /************************************************************ * Allocate a context that will hold all PG data for the procedure. ************************************************************/ ! proc_cxt = AllocSetContextCreateExtended(TopMemoryContext, ! NameStr(procStruct->proname), ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); /************************************************************ * Allocate and fill a new procedure description block. diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c index b7c24e3..990a33c 100644 *** a/src/pl/plpython/plpy_procedure.c --- b/src/pl/plpython/plpy_procedure.c *************** PLy_procedure_create(HeapTuple procTup, *** 166,174 **** } /* Create long-lived context that all procedure info will live in */ ! cxt = AllocSetContextCreate(TopMemoryContext, ! procName, ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(cxt); --- 166,175 ---- } /* Create long-lived context that all procedure info will live in */ ! cxt = AllocSetContextCreateExtended(TopMemoryContext, ! procName, ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(cxt); diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index e0792d9..8069784 100644 *** a/src/pl/tcl/pltcl.c --- b/src/pl/tcl/pltcl.c *************** compile_pltcl_function(Oid fn_oid, Oid t *** 1471,1479 **** * 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); /************************************************************ * Allocate and fill a new procedure description block. --- 1471,1480 ---- * Allocate a context that will hold all PG data for the procedure. * We use the internal proc name as the context name. ************************************************************/ ! proc_cxt = AllocSetContextCreateExtended(TopMemoryContext, ! internal_proname, ! MEMCONTEXT_COPY_NAME, ! ALLOCSET_SMALL_SIZES); /************************************************************ * Allocate and fill a new procedure description block.