Previous discussion: https://postgr.es/m/1407012053.15301.53.camel%40jeff-desktop
This patch introduces a way to ask a memory context how much memory it currently has allocated. Each time a new block (not an individual chunk, but a new malloc'd block) is allocated, it increments a struct member; and each time a block is free'd, it decrements the struct member. So it tracks blocks allocated by malloc, not what is actually used for chunks allocated by palloc. The purpose is for Memory Bounded Hash Aggregation, but it may be useful in more cases as we try to better adapt to and control memory usage at execution time. I ran the same tests as Robert did before[1] on my laptop[2]. The only difference is that I also set max_parallel_workers[_per_gather]=0 to be sure. I did 5 runs, alternating between memory-accounting and master, and I got the following results for "elapsed" (as reported by trace_sort): regression=# select version, min(s), max(s), percentile_disc(0.5) within group (order by s) as median, avg(s)::numeric(10,2) from tmp group by version; version | min | max | median | avg -------------------+-------+-------+--------+------- master | 13.92 | 14.40 | 14.06 | 14.12 memory accounting | 13.43 | 14.46 | 14.11 | 14.09 (2 rows) So, I don't see any significant performance impact for this patch in this test. That may be because: * It was never really significant except on PPC64. * I changed it to only update mem_allocated for the current context, not recursively for all parent contexts. It's now up to the function that reports memory usage to recurse or not (as needed). * A lot of changes to sort have happened since that time, so perhaps it's not a good test of memory contexts any more. pgbench didn't show any slowdown either. I also did another test with hash aggregation that uses significant memory (t10m is a table with 10M distinct values and work_mem is 1GB): postgres=# select (select (i, count(*)) from t10m group by i having count(*) > n) from (values(1),(2),(3),(4),(5)) as s(n); I didn't see any noticable difference there, either. Regards, Jeff Davis [1] https://postgr.es/m/CA%2BTgmobnu7XEn1gRdXnFo37P79bF%3DqLt46%3D37ajP3Cro9dBRaA%40mail.gmail.com [2] Linux jdavis 4.15.0-54-generic #58-Ubuntu SMP Mon Jun 24 10:55:24 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
From d7c8587620aad080afc904b54a3ebd3232f39b7c Mon Sep 17 00:00:00 2001 From: Jeff Davis <jda...@postgresql.org> Date: Fri, 1 Jun 2018 13:35:21 -0700 Subject: [PATCH] Memory accounting at block level. Track the memory allocated to a memory context in the form of blocks, and introduce a new function MemoryContextMemAllocated() to report it to the caller in bytes. This does not track individual chunks. --- src/backend/utils/mmgr/aset.c | 36 +++++++++++++++++++++++++++++ src/backend/utils/mmgr/generation.c | 18 +++++++++++---- src/backend/utils/mmgr/mcxt.c | 24 +++++++++++++++++++ src/backend/utils/mmgr/slab.c | 10 ++++++++ src/include/nodes/memnodes.h | 1 + src/include/utils/memutils.h | 1 + 6 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 6e4a343439..6e90493810 100644 --- a/src/backend/utils/mmgr/aset.c +++ b/src/backend/utils/mmgr/aset.c @@ -458,6 +458,9 @@ AllocSetContextCreateInternal(MemoryContext parent, parent, name); + ((MemoryContext) set)->mem_allocated = + set->keeper->endptr - ((char *) set); + return (MemoryContext) set; } } @@ -546,6 +549,8 @@ AllocSetContextCreateInternal(MemoryContext parent, parent, name); + ((MemoryContext) set)->mem_allocated = firstBlockSize; + return (MemoryContext) set; } @@ -604,6 +609,8 @@ AllocSetReset(MemoryContext context) else { /* Normal case, release the block */ + context->mem_allocated -= block->endptr - ((char*) block); + #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif @@ -612,6 +619,8 @@ AllocSetReset(MemoryContext context) block = next; } + Assert(context->mem_allocated == set->keeper->endptr - ((char *) set)); + /* Reset block size allocation sequence, too */ set->nextBlockSize = set->initBlockSize; } @@ -688,11 +697,16 @@ AllocSetDelete(MemoryContext context) #endif if (block != set->keeper) + { + context->mem_allocated -= block->endptr - ((char *) block); free(block); + } block = next; } + Assert(context->mem_allocated == set->keeper->endptr - ((char *) set)); + /* Finally, free the context header, including the keeper block */ free(set); } @@ -733,6 +747,9 @@ AllocSetAlloc(MemoryContext context, Size size) block = (AllocBlock) malloc(blksize); if (block == NULL) return NULL; + + context->mem_allocated += blksize; + block->aset = set; block->freeptr = block->endptr = ((char *) block) + blksize; @@ -928,6 +945,8 @@ AllocSetAlloc(MemoryContext context, Size size) if (block == NULL) return NULL; + context->mem_allocated += blksize; + block->aset = set; block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ; block->endptr = ((char *) block) + blksize; @@ -1028,6 +1047,9 @@ AllocSetFree(MemoryContext context, void *pointer) set->blocks = block->next; if (block->next) block->next->prev = block->prev; + + context->mem_allocated -= block->endptr - ((char*) block); + #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif @@ -1144,6 +1166,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ); Size chksize; Size blksize; + Size oldblksize; /* * Try to verify that we have a sane block pointer: it should @@ -1159,6 +1182,8 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) /* Do the realloc */ chksize = MAXALIGN(size); blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; + oldblksize = block->endptr - ((char *)block); + block = (AllocBlock) realloc(block, blksize); if (block == NULL) { @@ -1166,6 +1191,9 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); return NULL; } + + context->mem_allocated += blksize - oldblksize; + block->freeptr = block->endptr = ((char *) block) + blksize; /* Update pointers since block has likely been moved */ @@ -1383,6 +1411,7 @@ AllocSetCheck(MemoryContext context) const char *name = set->header.name; AllocBlock prevblock; AllocBlock block; + int64 total_allocated = 0; for (prevblock = NULL, block = set->blocks; block != NULL; @@ -1393,6 +1422,11 @@ AllocSetCheck(MemoryContext context) long blk_data = 0; long nchunks = 0; + if (set->keeper == block) + total_allocated += block->endptr - ((char *) set); + else + total_allocated += block->endptr - ((char *) block); + /* * Empty block - empty can be keeper-block only */ @@ -1479,6 +1513,8 @@ AllocSetCheck(MemoryContext context) elog(WARNING, "problem in alloc set %s: found inconsistent memory block %p", name, block); } + + Assert(total_allocated == context->mem_allocated); } #endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c index eaacafb7be..e84599cc17 100644 --- a/src/backend/utils/mmgr/generation.c +++ b/src/backend/utils/mmgr/generation.c @@ -300,7 +300,7 @@ GenerationReset(MemoryContext context) #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->blksize); #endif - + context->mem_allocated -= block->blksize; free(block); } @@ -352,6 +352,8 @@ GenerationAlloc(MemoryContext context, Size size) if (block == NULL) return NULL; + context->mem_allocated += blksize; + /* block with a single (used) chunk */ block->blksize = blksize; block->nchunks = 1; @@ -407,6 +409,8 @@ GenerationAlloc(MemoryContext context, Size size) if (block == NULL) return NULL; + context->mem_allocated += blksize; + block->blksize = blksize; block->nchunks = 0; block->nfree = 0; @@ -522,6 +526,7 @@ GenerationFree(MemoryContext context, void *pointer) if (set->block == block) set->block = NULL; + context->mem_allocated -= block->blksize; free(block); } @@ -743,9 +748,10 @@ GenerationStats(MemoryContext context, static void GenerationCheck(MemoryContext context) { - GenerationContext *gen = (GenerationContext *) context; - const char *name = context->name; - dlist_iter iter; + GenerationContext *gen = (GenerationContext *) context; + const char *name = context->name; + dlist_iter iter; + int64 total_allocated = 0; /* walk all blocks in this context */ dlist_foreach(iter, &gen->blocks) @@ -755,6 +761,8 @@ GenerationCheck(MemoryContext context) nchunks; char *ptr; + total_allocated += block->blksize; + /* * nfree > nchunks is surely wrong, and we don't expect to see * equality either, because such a block should have gotten freed. @@ -833,6 +841,8 @@ GenerationCheck(MemoryContext context) elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d", name, nfree, block, block->nfree); } + + Assert(total_allocated == context->mem_allocated); } #endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index b07be12236..27417af548 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -462,6 +462,29 @@ MemoryContextIsEmpty(MemoryContext context) return context->methods->is_empty(context); } +/* + * Find the memory allocated to blocks for this memory context. If recurse is + * true, also include children. + */ +int64 +MemoryContextMemAllocated(MemoryContext context, bool recurse) +{ + int64 total = context->mem_allocated; + + AssertArg(MemoryContextIsValid(context)); + + if (recurse) + { + MemoryContext child = context->firstchild; + for (child = context->firstchild; + child != NULL; + child = child->nextchild) + total += MemoryContextMemAllocated(child, true); + } + + return total; +} + /* * MemoryContextStats * Print statistics about the named context and all its descendants. @@ -736,6 +759,7 @@ MemoryContextCreate(MemoryContext node, node->methods = methods; node->parent = parent; node->firstchild = NULL; + node->mem_allocated = 0; node->prevchild = NULL; node->name = name; node->ident = NULL; diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c index e23669fb4f..046515e864 100644 --- a/src/backend/utils/mmgr/slab.c +++ b/src/backend/utils/mmgr/slab.c @@ -305,12 +305,14 @@ SlabReset(MemoryContext context) #endif free(block); slab->nblocks--; + context->mem_allocated -= slab->blockSize; } } slab->minFreeChunks = 0; Assert(slab->nblocks == 0); + Assert(context->mem_allocated == 0); } /* @@ -388,6 +390,7 @@ SlabAlloc(MemoryContext context, Size size) slab->minFreeChunks = slab->chunksPerBlock; slab->nblocks += 1; + context->mem_allocated += slab->blockSize; } /* grab the block from the freelist (even the new block is there) */ @@ -480,6 +483,9 @@ SlabAlloc(MemoryContext context, Size size) #endif SlabAllocInfo(slab, chunk); + + Assert(slab->nblocks * slab->blockSize == context->mem_allocated); + return SlabChunkGetPointer(chunk); } @@ -555,11 +561,13 @@ SlabFree(MemoryContext context, void *pointer) { free(block); slab->nblocks--; + context->mem_allocated -= slab->blockSize; } else dlist_push_head(&slab->freelist[block->nfree], &block->node); Assert(slab->nblocks >= 0); + Assert(slab->nblocks * slab->blockSize == context->mem_allocated); } /* @@ -782,6 +790,8 @@ SlabCheck(MemoryContext context) name, block->nfree, block, nfree); } } + + Assert(slab->nblocks * slab->blockSize == context->mem_allocated); } #endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h index dbae98d3d9..df0ae3625c 100644 --- a/src/include/nodes/memnodes.h +++ b/src/include/nodes/memnodes.h @@ -79,6 +79,7 @@ typedef struct MemoryContextData /* these two fields are placed here to minimize alignment wastage: */ bool isReset; /* T = no space alloced since last reset */ bool allowInCritSection; /* allow palloc in critical section */ + int64 mem_allocated; /* track memory allocated for this context */ const MemoryContextMethods *methods; /* virtual function table */ MemoryContext parent; /* NULL if no parent (toplevel context) */ MemoryContext firstchild; /* head of linked list of children */ diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index ffe6de536e..6a837bc990 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -82,6 +82,7 @@ extern void MemoryContextSetParent(MemoryContext context, extern Size GetMemoryChunkSpace(void *pointer); extern MemoryContext MemoryContextGetParent(MemoryContext context); extern bool MemoryContextIsEmpty(MemoryContext context); +extern int64 MemoryContextMemAllocated(MemoryContext context, bool recurse); extern void MemoryContextStats(MemoryContext context); extern void MemoryContextStatsDetail(MemoryContext context, int max_children); extern void MemoryContextAllowInCriticalSection(MemoryContext context, -- 2.17.1