On Mon, 2020-04-06 at 23:39 +1200, David Rowley wrote: > 1. The comment mentions "passthru", but you've removed that > parameter.
Fixed, thank you. > 2. I don't think MemoryContextCount is the best name for this > function. When I saw: I've gone back and forth on naming a bit. The right name, in my opinion, is MemoryContextStats(), but that's taken by something that should be called MemoryContextReport(). But I didn't want to change that as it would probably annoy a lot of people who are used to calling MemoryContextStats() from gdb. I changed the new function to be called MemoryContextGetCounters(), which is more directly what it's doing. "Telemetry" makes me think more of a stream of information rather than a particular point in time. > 3. I feel like it would be nicer if you didn't change the "count" > methods to return a MemoryContextCounters. It means you may need to > zero a struct for each level, assign the values, then add them to the > total. If you were just to zero the struct in MemoryContextCount() > then pass it into the count function, then you could just have it do > all the += work. It would reduce the code in MemoryContextCount() > too. I changed it to use a pointer out parameter, but I don't think an in/out parameter is quite right there. MemoryContextStats() ends up using both the per-context counters as well as the totals, so it's not helpful to return just the totals. > 4. Do you think it would be better to have two separate functions for > MemoryContextCount(), a recursive version and a non-recursive > version. I could, but right now the only caller passes recurse=true, so I'm not really eliminating any code in that path by specializing the functions. Are you thinking about performance or you just think it would be better to have two entry points? > 5. For performance testing, I tried using the following table with > 1MB > work_mem then again with 1GB work_mem. I wondered if making the > accounting more complex would cause a slowdown in nodeAgg.c, as I see > we're calling this function each time we get a tuple that belongs in > a > new group. The 1MB test is likely not such a great way to measure > this > since we do spill to disk in that case and the changing in accounting > means we do that at a slightly different time, but the 1GB test does > not spill and it's a bit slower. I think this was because I was previously returning a struct. By just passing a pointer as an out param, it seems to have mitigated it, but not completely eliminated the cost (< 2% in my tests). I was using an OFFSET 100000000 instead of EXPLAIN ANALYZE in my test measurements because it was less noisy, and I focused on the 1GB test for the reason you mention. I also addressed Andres's comments: * changed the name of the flags from MCXT_STAT to MCXT_COUNT * changed ((1<<6)-1) to ~0 * removed unnecessary branches from the GetCounters method * expanded some comments Regards, Jeff Davis
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 4e100e5755f..fd205071ca1 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -414,6 +414,7 @@ static void agg_fill_hash_table(AggState *aggstate); static bool agg_refill_hash_table(AggState *aggstate); static TupleTableSlot *agg_retrieve_hash_table(AggState *aggstate); static TupleTableSlot *agg_retrieve_hash_table_in_memory(AggState *aggstate); +static Size hash_agg_check_memory(MemoryContext context); static void hash_agg_check_limits(AggState *aggstate); static void hash_agg_enter_spill_mode(AggState *aggstate); static void hash_agg_update_metrics(AggState *aggstate, bool from_tape, @@ -1792,6 +1793,25 @@ hash_agg_set_limits(double hashentrysize, uint64 input_groups, int used_bits, *ngroups_limit = 1; } +/* + * Get the memory consumed by the memory context. Consider all memory except + * "newspace", which is the untouched part of a new block allocation by the + * memory context itself. The algorithm has little control over how eagerly + * the memory context allocates new memory, so it doesn't make sense to count + * it against work_mem. + */ +static Size +hash_agg_check_memory(MemoryContext context) +{ + MemoryContextCounters counters; + uint32 flags = (MCXT_COUNT_TOTALSPACE | + MCXT_COUNT_NEWSPACE); + + MemoryContextGetCounters(context, flags, true, &counters); + + return counters.totalspace - counters.newspace; +} + /* * hash_agg_check_limits * @@ -1802,11 +1822,17 @@ hash_agg_set_limits(double hashentrysize, uint64 input_groups, int used_bits, static void hash_agg_check_limits(AggState *aggstate) { - uint64 ngroups = aggstate->hash_ngroups_current; - Size meta_mem = MemoryContextMemAllocated( - aggstate->hash_metacxt, true); - Size hash_mem = MemoryContextMemAllocated( - aggstate->hashcontext->ecxt_per_tuple_memory, true); + uint64 ngroups = aggstate->hash_ngroups_current; + Size meta_mem; + Size hash_mem; + + /* already in spill mode; nothing to do */ + if (aggstate->hash_spill_mode) + return; + + meta_mem = hash_agg_check_memory(aggstate->hash_metacxt); + hash_mem = hash_agg_check_memory( + aggstate->hashcontext->ecxt_per_tuple_memory); /* * Don't spill unless there's at least one group in the hash table so we @@ -1874,11 +1900,11 @@ hash_agg_update_metrics(AggState *aggstate, bool from_tape, int npartitions) return; /* memory for the hash table itself */ - meta_mem = MemoryContextMemAllocated(aggstate->hash_metacxt, true); + meta_mem = hash_agg_check_memory(aggstate->hash_metacxt); /* memory for the group keys and transition states */ - hash_mem = MemoryContextMemAllocated( - aggstate->hashcontext->ecxt_per_tuple_memory, true); + hash_mem = hash_agg_check_memory( + aggstate->hashcontext->ecxt_per_tuple_memory); /* memory for read/write tape buffers, if spilled */ buffer_mem = npartitions * HASHAGG_WRITE_BUFFER_SIZE; diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index c0623f106d2..1dfc6126ad4 100644 --- a/src/backend/utils/mmgr/aset.c +++ b/src/backend/utils/mmgr/aset.c @@ -132,6 +132,8 @@ typedef struct AllocSetContext Size maxBlockSize; /* maximum block size */ Size nextBlockSize; /* next block size to allocate */ Size allocChunkLimit; /* effective chunk size limit */ + Size memAllocated; /* track memory allocated for this context */ + uint64 nChunks; /* total number of chunks */ 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 */ @@ -273,9 +275,8 @@ static void AllocSetReset(MemoryContext context); static void AllocSetDelete(MemoryContext context); static Size AllocSetGetChunkSpace(MemoryContext context, void *pointer); static bool AllocSetIsEmpty(MemoryContext context); -static void AllocSetStats(MemoryContext context, - MemoryStatsPrintFunc printfunc, void *passthru, - MemoryContextCounters *totals); +static void AllocSetGetCounters(MemoryContext context, uint32 flags, + MemoryContextCounters *counters); #ifdef MEMORY_CONTEXT_CHECKING static void AllocSetCheck(MemoryContext context); @@ -292,7 +293,7 @@ static const MemoryContextMethods AllocSetMethods = { AllocSetDelete, AllocSetGetChunkSpace, AllocSetIsEmpty, - AllocSetStats + AllocSetGetCounters #ifdef MEMORY_CONTEXT_CHECKING ,AllocSetCheck #endif @@ -464,8 +465,8 @@ AllocSetContextCreateInternal(MemoryContext parent, parent, name); - ((MemoryContext) set)->mem_allocated = - set->keeper->endptr - ((char *) set); + set->memAllocated = set->keeper->endptr - ((char *) set); + set->nChunks = 0; return (MemoryContext) set; } @@ -555,7 +556,8 @@ AllocSetContextCreateInternal(MemoryContext parent, parent, name); - ((MemoryContext) set)->mem_allocated = firstBlockSize; + set->memAllocated = firstBlockSize; + set->nChunks = 0; return (MemoryContext) set; } @@ -617,7 +619,7 @@ AllocSetReset(MemoryContext context) else { /* Normal case, release the block */ - context->mem_allocated -= block->endptr - ((char*) block); + set->memAllocated -= block->endptr - ((char*) block); #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); @@ -627,7 +629,9 @@ AllocSetReset(MemoryContext context) block = next; } - Assert(context->mem_allocated == keepersize); + Assert(set->memAllocated == keepersize); + + set->nChunks = 0; /* Reset block size allocation sequence, too */ set->nextBlockSize = set->initBlockSize; @@ -703,7 +707,7 @@ AllocSetDelete(MemoryContext context) AllocBlock next = block->next; if (block != set->keeper) - context->mem_allocated -= block->endptr - ((char *) block); + set->memAllocated -= block->endptr - ((char *) block); #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); @@ -715,7 +719,7 @@ AllocSetDelete(MemoryContext context) block = next; } - Assert(context->mem_allocated == keepersize); + Assert(set->memAllocated == keepersize); /* Finally, free the context header, including the keeper block */ free(set); @@ -758,7 +762,7 @@ AllocSetAlloc(MemoryContext context, Size size) if (block == NULL) return NULL; - context->mem_allocated += blksize; + set->memAllocated += blksize; block->aset = set; block->freeptr = block->endptr = ((char *) block) + blksize; @@ -805,6 +809,7 @@ AllocSetAlloc(MemoryContext context, Size size) /* Disallow external access to private part of chunk header. */ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); + set->nChunks++; return AllocChunkGetPointer(chunk); } @@ -844,6 +849,8 @@ AllocSetAlloc(MemoryContext context, Size size) /* Disallow external access to private part of chunk header. */ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); + /* chunk already existed; don't increment nChunks */ + return AllocChunkGetPointer(chunk); } @@ -906,6 +913,7 @@ AllocSetAlloc(MemoryContext context, Size size) #endif chunk->aset = (void *) set->freelist[a_fidx]; set->freelist[a_fidx] = chunk; + set->nChunks++; } /* Mark that we need to create a new block */ @@ -955,7 +963,7 @@ AllocSetAlloc(MemoryContext context, Size size) if (block == NULL) return NULL; - context->mem_allocated += blksize; + set->memAllocated += blksize; block->aset = set; block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ; @@ -1005,6 +1013,7 @@ AllocSetAlloc(MemoryContext context, Size size) /* Disallow external access to private part of chunk header. */ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); + set->nChunks++; return AllocChunkGetPointer(chunk); } @@ -1058,7 +1067,8 @@ AllocSetFree(MemoryContext context, void *pointer) if (block->next) block->next->prev = block->prev; - context->mem_allocated -= block->endptr - ((char*) block); + set->memAllocated -= block->endptr - ((char*) block); + set->nChunks--; #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); @@ -1161,8 +1171,8 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) } /* updated separately, not to underflow when (oldblksize > blksize) */ - context->mem_allocated -= oldblksize; - context->mem_allocated += blksize; + set->memAllocated -= oldblksize; + set->memAllocated += blksize; block->freeptr = block->endptr = ((char *) block) + blksize; @@ -1356,65 +1366,53 @@ AllocSetIsEmpty(MemoryContext context) } /* - * AllocSetStats - * Compute stats about memory consumption of an allocset. - * - * printfunc: if not NULL, pass a human-readable stats string to this. - * passthru: pass this pointer through to printfunc. - * totals: if not NULL, add stats about this context into *totals. + * AllocSetGetCounters + * Compute and return memory consumption counters. Specify the needed + * counters by setting the appropriate flags. */ static void -AllocSetStats(MemoryContext context, - MemoryStatsPrintFunc printfunc, void *passthru, - MemoryContextCounters *totals) +AllocSetGetCounters(MemoryContext context, uint32 flags, + MemoryContextCounters *counters) { - AllocSet set = (AllocSet) context; - Size nblocks = 0; + AllocSet set = (AllocSet) context; + Size nblocks = 0; Size freechunks = 0; - Size totalspace; - Size freespace = 0; - AllocBlock block; - int fidx; + Size freespace = 0; - /* Include context header in totalspace */ - totalspace = MAXALIGN(sizeof(AllocSetContext)); - - for (block = set->blocks; block != NULL; block = block->next) - { - nblocks++; - totalspace += block->endptr - ((char *) block); - freespace += block->endptr - block->freeptr; - } - for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++) + if (flags & (MCXT_COUNT_NBLOCKS | MCXT_COUNT_FREESPACE)) { - AllocChunk chunk; + AllocBlock block; - for (chunk = set->freelist[fidx]; chunk != NULL; - chunk = (AllocChunk) chunk->aset) + for (block = set->blocks; block != NULL; block = block->next) { - freechunks++; - freespace += chunk->size + ALLOC_CHUNKHDRSZ; + nblocks++; + freespace += block->endptr - block->freeptr; } } - if (printfunc) + if (flags & (MCXT_COUNT_FREECHUNKS | MCXT_COUNT_FREESPACE)) { - char stats_string[200]; + int fidx; - snprintf(stats_string, sizeof(stats_string), - "%zu total in %zd blocks; %zu free (%zd chunks); %zu used", - totalspace, nblocks, freespace, freechunks, - totalspace - freespace); - printfunc(context, passthru, stats_string); - } + for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++) + { + AllocChunk chunk; - if (totals) - { - totals->nblocks += nblocks; - totals->freechunks += freechunks; - totals->totalspace += totalspace; - totals->freespace += freespace; + for (chunk = set->freelist[fidx]; chunk != NULL; + chunk = (AllocChunk) chunk->aset) + { + freechunks++; + freespace += chunk->size + ALLOC_CHUNKHDRSZ; + } + } } + + counters->nblocks = nblocks; + counters->nchunks = set->nChunks; + counters->freechunks = freechunks; + counters->totalspace = set->memAllocated; + counters->freespace = freespace; + counters->newspace = set->blocks->endptr - set->blocks->freeptr; } @@ -1436,6 +1434,7 @@ AllocSetCheck(MemoryContext context) AllocBlock prevblock; AllocBlock block; Size total_allocated = 0; + uint64 total_nchunks = 0; for (prevblock = NULL, block = set->blocks; block != NULL; @@ -1529,6 +1528,7 @@ AllocSetCheck(MemoryContext context) blk_data += chsize; nchunks++; + total_nchunks++; bpoz += ALLOC_CHUNKHDRSZ + chsize; } @@ -1538,7 +1538,8 @@ AllocSetCheck(MemoryContext context) name, block); } - Assert(total_allocated == context->mem_allocated); + Assert(total_allocated == set->memAllocated); + Assert(total_nchunks == set->nChunks); } #endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c index 56651d06931..f1a989dd1e6 100644 --- a/src/backend/utils/mmgr/generation.c +++ b/src/backend/utils/mmgr/generation.c @@ -61,6 +61,7 @@ typedef struct GenerationContext /* Generational context parameters */ Size blockSize; /* standard block size */ + Size memAllocated; /* track memory allocated for this context */ GenerationBlock *block; /* current (most recently allocated) block */ dlist_head blocks; /* list of blocks */ @@ -153,9 +154,8 @@ static void GenerationReset(MemoryContext context); static void GenerationDelete(MemoryContext context); static Size GenerationGetChunkSpace(MemoryContext context, void *pointer); static bool GenerationIsEmpty(MemoryContext context); -static void GenerationStats(MemoryContext context, - MemoryStatsPrintFunc printfunc, void *passthru, - MemoryContextCounters *totals); +static void GenerationGetCounters(MemoryContext context, uint32 flags, + MemoryContextCounters *counters); #ifdef MEMORY_CONTEXT_CHECKING static void GenerationCheck(MemoryContext context); @@ -172,7 +172,7 @@ static const MemoryContextMethods GenerationMethods = { GenerationDelete, GenerationGetChunkSpace, GenerationIsEmpty, - GenerationStats + GenerationGetCounters #ifdef MEMORY_CONTEXT_CHECKING ,GenerationCheck #endif @@ -258,6 +258,7 @@ GenerationContextCreate(MemoryContext parent, /* Fill in GenerationContext-specific header fields */ set->blockSize = blockSize; + set->memAllocated = 0; set->block = NULL; dlist_init(&set->blocks); @@ -297,7 +298,7 @@ GenerationReset(MemoryContext context) dlist_delete(miter.cur); - context->mem_allocated -= block->blksize; + set->memAllocated -= block->blksize; #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->blksize); @@ -354,7 +355,7 @@ GenerationAlloc(MemoryContext context, Size size) if (block == NULL) return NULL; - context->mem_allocated += blksize; + set->memAllocated += blksize; /* block with a single (used) chunk */ block->blksize = blksize; @@ -411,7 +412,7 @@ GenerationAlloc(MemoryContext context, Size size) if (block == NULL) return NULL; - context->mem_allocated += blksize; + set->memAllocated += blksize; block->blksize = blksize; block->nchunks = 0; @@ -528,7 +529,7 @@ GenerationFree(MemoryContext context, void *pointer) if (set->block == block) set->block = NULL; - context->mem_allocated -= block->blksize; + set->memAllocated -= block->blksize; free(block); } @@ -679,61 +680,43 @@ GenerationIsEmpty(MemoryContext context) } /* - * GenerationStats - * Compute stats about memory consumption of a Generation context. - * - * printfunc: if not NULL, pass a human-readable stats string to this. - * passthru: pass this pointer through to printfunc. - * totals: if not NULL, add stats about this context into *totals. - * - * XXX freespace only accounts for empty space at the end of the block, not - * space of freed chunks (which is unknown). + * GenerationGetCounters + * Compute and return memory consumption counters. Specify the needed + * counters by setting the appropriate flags. */ static void -GenerationStats(MemoryContext context, - MemoryStatsPrintFunc printfunc, void *passthru, - MemoryContextCounters *totals) +GenerationGetCounters(MemoryContext context, uint32 flags, + MemoryContextCounters *counters) { - GenerationContext *set = (GenerationContext *) context; - Size nblocks = 0; - Size nchunks = 0; - Size nfreechunks = 0; - Size totalspace; - Size freespace = 0; - dlist_iter iter; - - /* Include context header in totalspace */ - totalspace = MAXALIGN(sizeof(GenerationContext)); - - dlist_foreach(iter, &set->blocks) + GenerationContext *set = (GenerationContext *) context; + uint64 nblocks = 0; + uint64 nchunks = 0; + uint64 freechunks = 0; + Size freespace = 0; + + if (flags & (MCXT_COUNT_NBLOCKS | MCXT_COUNT_NCHUNKS | + MCXT_COUNT_FREECHUNKS | MCXT_COUNT_FREESPACE)) { - GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); + dlist_iter iter; - nblocks++; - nchunks += block->nchunks; - nfreechunks += block->nfree; - totalspace += block->blksize; - freespace += (block->endptr - block->freeptr); - } - - if (printfunc) - { - char stats_string[200]; + dlist_foreach(iter, &set->blocks) + { + GenerationBlock *block = dlist_container( + GenerationBlock, node, iter.cur); - snprintf(stats_string, sizeof(stats_string), - "%zu total in %zd blocks (%zd chunks); %zu free (%zd chunks); %zu used", - totalspace, nblocks, nchunks, freespace, - nfreechunks, totalspace - freespace); - printfunc(context, passthru, stats_string); + nblocks++; + nchunks += block->nchunks; + freechunks += block->nfree; + freespace += (block->endptr - block->freeptr); + } } - if (totals) - { - totals->nblocks += nblocks; - totals->freechunks += nfreechunks; - totals->totalspace += totalspace; - totals->freespace += freespace; - } + counters->nblocks = nblocks; + counters->nchunks = nchunks; + counters->freechunks = freechunks; + counters->totalspace = set->memAllocated; + counters->freespace = freespace; + counters->newspace = set->block->endptr - set->block->freeptr; } @@ -844,7 +827,7 @@ GenerationCheck(MemoryContext context) name, nfree, block, block->nfree); } - Assert(total_allocated == context->mem_allocated); + Assert(total_allocated == gen->memAllocated); } #endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 9e24fec72d6..1708413c46c 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -56,7 +56,7 @@ static void MemoryContextCallResetCallbacks(MemoryContext context); static void MemoryContextStatsInternal(MemoryContext context, int level, bool print, int max_children, MemoryContextCounters *totals); -static void MemoryContextStatsPrint(MemoryContext context, void *passthru, +static void MemoryContextStatsPrint(MemoryContext context, int level, const char *stats_string); /* @@ -463,27 +463,39 @@ MemoryContextIsEmpty(MemoryContext context) } /* - * Find the memory allocated to blocks for this memory context. If recurse is - * true, also include children. + * MemoryContextCount + * Return statistics about this memory context, optionally recursing to + * children. Flags are defined in memnodes.h and specify which statistics + * are required. */ -Size -MemoryContextMemAllocated(MemoryContext context, bool recurse) +void +MemoryContextGetCounters(MemoryContext context, uint32 flags, bool recurse, + MemoryContextCounters *counters) { - Size total = context->mem_allocated; - AssertArg(MemoryContextIsValid(context)); + context->methods->get_counters(context, flags, counters); + if (recurse) { - MemoryContext child = context->firstchild; + MemoryContext child; for (child = context->firstchild; child != NULL; child = child->nextchild) - total += MemoryContextMemAllocated(child, true); - } + { + MemoryContextCounters child_counters; + + child->methods->get_counters(child, flags, &child_counters); - return total; + counters->nblocks += child_counters.nblocks; + counters->nchunks += child_counters.nchunks; + counters->freechunks += child_counters.freechunks; + counters->totalspace += child_counters.totalspace; + counters->freespace += child_counters.freespace; + counters->newspace += child_counters.newspace; + } + } } /* @@ -516,9 +528,10 @@ MemoryContextStatsDetail(MemoryContext context, int max_children) MemoryContextStatsInternal(context, 0, true, max_children, &grand_totals); fprintf(stderr, - "Grand total: %zu bytes in %zd blocks; %zu free (%zd chunks); %zu used\n", + "Grand total: %zu bytes in %zd blocks (%zd chunks); %zu free (%zd chunks); %zu used\n", grand_totals.totalspace, grand_totals.nblocks, - grand_totals.freespace, grand_totals.freechunks, + grand_totals.nchunks, grand_totals.freespace, + grand_totals.freechunks, grand_totals.totalspace - grand_totals.freespace); } @@ -534,24 +547,42 @@ MemoryContextStatsInternal(MemoryContext context, int level, bool print, int max_children, MemoryContextCounters *totals) { - MemoryContextCounters local_totals; + MemoryContextCounters excess_summary = {0}; + MemoryContextCounters current; MemoryContext child; int ichild; + AssertArg(MemoryContextIsValid(context)); /* Examine the context itself */ - context->methods->stats(context, - print ? MemoryContextStatsPrint : NULL, - (void *) &level, - totals); + context->methods->get_counters(context, MCXT_COUNT_ALL, ¤t); + + if (print) + { + char stats_string[200]; + snprintf(stats_string, sizeof(stats_string), + "%zu total in %zd blocks (%zd chunks); %zu free (%zd chunks); %zu used", + current.totalspace, current.nblocks, current.nchunks, + current.freespace, current.freechunks, + current.totalspace - current.freespace); + MemoryContextStatsPrint(context, level, stats_string); + } + + if (totals) + { + totals->nblocks += current.nblocks; + totals->nchunks += current.nchunks; + totals->freechunks += current.freechunks; + totals->totalspace += current.totalspace; + totals->freespace += current.freespace; + totals->newspace += current.newspace; + } /* * Examine children. If there are more than max_children of them, we do * not print the rest explicitly, but just summarize them. */ - memset(&local_totals, 0, sizeof(local_totals)); - for (child = context->firstchild, ichild = 0; child != NULL; child = child->nextchild, ichild++) @@ -563,7 +594,7 @@ MemoryContextStatsInternal(MemoryContext context, int level, else MemoryContextStatsInternal(child, level + 1, false, max_children, - &local_totals); + &excess_summary); } /* Deal with excess children */ @@ -578,35 +609,33 @@ MemoryContextStatsInternal(MemoryContext context, int level, fprintf(stderr, "%d more child contexts containing %zu total in %zd blocks; %zu free (%zd chunks); %zu used\n", ichild - max_children, - local_totals.totalspace, - local_totals.nblocks, - local_totals.freespace, - local_totals.freechunks, - local_totals.totalspace - local_totals.freespace); + excess_summary.totalspace, + excess_summary.nblocks, + excess_summary.freespace, + excess_summary.freechunks, + excess_summary.totalspace - excess_summary.freespace); } if (totals) { - totals->nblocks += local_totals.nblocks; - totals->freechunks += local_totals.freechunks; - totals->totalspace += local_totals.totalspace; - totals->freespace += local_totals.freespace; + totals->nblocks += excess_summary.nblocks; + totals->nchunks += excess_summary.nchunks; + totals->freechunks += excess_summary.freechunks; + totals->totalspace += excess_summary.totalspace; + totals->freespace += excess_summary.freespace; + totals->newspace += excess_summary.newspace; } } } /* * MemoryContextStatsPrint - * Print callback used by MemoryContextStatsInternal - * - * For now, the passthru pointer just points to "int level"; later we might - * make that more complicated. + * Print function used by MemoryContextStatsInternal */ static void -MemoryContextStatsPrint(MemoryContext context, void *passthru, +MemoryContextStatsPrint(MemoryContext context, int level, const char *stats_string) { - int level = *(int *) passthru; const char *name = context->name; const char *ident = context->ident; int i; @@ -760,7 +789,6 @@ 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 c928476c479..acab22ffa49 100644 --- a/src/backend/utils/mmgr/slab.c +++ b/src/backend/utils/mmgr/slab.c @@ -67,6 +67,7 @@ typedef struct SlabContext Size fullChunkSize; /* chunk size including header and alignment */ Size blockSize; /* block size */ Size headerSize; /* allocated size of context header */ + Size memAllocated; /* track memory allocated for this context */ int chunksPerBlock; /* number of chunks per block */ int minFreeChunks; /* min number of free chunks in any block */ int nblocks; /* number of blocks allocated */ @@ -133,9 +134,8 @@ static void SlabReset(MemoryContext context); static void SlabDelete(MemoryContext context); static Size SlabGetChunkSpace(MemoryContext context, void *pointer); static bool SlabIsEmpty(MemoryContext context); -static void SlabStats(MemoryContext context, - MemoryStatsPrintFunc printfunc, void *passthru, - MemoryContextCounters *totals); +static void SlabGetCounters(MemoryContext context, uint32 flags, + MemoryContextCounters *counters); #ifdef MEMORY_CONTEXT_CHECKING static void SlabCheck(MemoryContext context); #endif @@ -151,7 +151,7 @@ static const MemoryContextMethods SlabMethods = { SlabDelete, SlabGetChunkSpace, SlabIsEmpty, - SlabStats + SlabGetCounters #ifdef MEMORY_CONTEXT_CHECKING ,SlabCheck #endif @@ -262,6 +262,7 @@ SlabContextCreate(MemoryContext parent, slab->fullChunkSize = fullChunkSize; slab->blockSize = blockSize; slab->headerSize = headerSize; + slab->memAllocated = 0; slab->chunksPerBlock = chunksPerBlock; slab->minFreeChunks = 0; slab->nblocks = 0; @@ -322,14 +323,14 @@ SlabReset(MemoryContext context) #endif free(block); slab->nblocks--; - context->mem_allocated -= slab->blockSize; + slab->memAllocated -= slab->blockSize; } } slab->minFreeChunks = 0; Assert(slab->nblocks == 0); - Assert(context->mem_allocated == 0); + Assert(slab->memAllocated == 0); } /* @@ -407,7 +408,7 @@ SlabAlloc(MemoryContext context, Size size) slab->minFreeChunks = slab->chunksPerBlock; slab->nblocks += 1; - context->mem_allocated += slab->blockSize; + slab->memAllocated += slab->blockSize; } /* grab the block from the freelist (even the new block is there) */ @@ -501,7 +502,7 @@ SlabAlloc(MemoryContext context, Size size) SlabAllocInfo(slab, chunk); - Assert(slab->nblocks * slab->blockSize == context->mem_allocated); + Assert(slab->nblocks * slab->blockSize == slab->memAllocated); return SlabChunkGetPointer(chunk); } @@ -578,13 +579,13 @@ SlabFree(MemoryContext context, void *pointer) { free(block); slab->nblocks--; - context->mem_allocated -= slab->blockSize; + slab->memAllocated -= slab->blockSize; } else dlist_push_head(&slab->freelist[block->nfree], &block->node); Assert(slab->nblocks >= 0); - Assert(slab->nblocks * slab->blockSize == context->mem_allocated); + Assert(slab->nblocks * slab->blockSize == slab->memAllocated); } /* @@ -645,61 +646,48 @@ SlabIsEmpty(MemoryContext context) } /* - * SlabStats - * Compute stats about memory consumption of a Slab context. - * - * printfunc: if not NULL, pass a human-readable stats string to this. - * passthru: pass this pointer through to printfunc. - * totals: if not NULL, add stats about this context into *totals. + * SlabGetCounters + * Compute and return memory consumption counters. Specify the needed + * counters by setting the appropriate flags. */ static void -SlabStats(MemoryContext context, - MemoryStatsPrintFunc printfunc, void *passthru, - MemoryContextCounters *totals) +SlabGetCounters(MemoryContext context, uint32 flags, + MemoryContextCounters *counters) { - 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++) + SlabContext *slab = castNode(SlabContext, context); + uint64 nblocks = 0; + uint64 nchunks = 0; + uint64 freechunks = 0; + Size freespace = 0; + + if (flags & (MCXT_COUNT_NBLOCKS | MCXT_COUNT_NCHUNKS | + MCXT_COUNT_FREECHUNKS | MCXT_COUNT_FREESPACE)) { - dlist_iter iter; + int i; - dlist_foreach(iter, &slab->freelist[i]) + for (i = 0; i <= slab->chunksPerBlock; i++) { - SlabBlock *block = dlist_container(SlabBlock, node, iter.cur); + dlist_iter iter; - nblocks++; - totalspace += slab->blockSize; - freespace += slab->fullChunkSize * block->nfree; - freechunks += block->nfree; - } - } - - if (printfunc) - { - char stats_string[200]; + dlist_foreach(iter, &slab->freelist[i]) + { + SlabBlock *block = dlist_container(SlabBlock, node, iter.cur); - snprintf(stats_string, sizeof(stats_string), - "%zu total in %zd blocks; %zu free (%zd chunks); %zu used", - totalspace, nblocks, freespace, freechunks, - totalspace - freespace); - printfunc(context, passthru, stats_string); + nblocks++; + nchunks += slab->chunksPerBlock; + freespace += slab->fullChunkSize * block->nfree; + freechunks += block->nfree; + } + } } - if (totals) - { - totals->nblocks += nblocks; - totals->freechunks += freechunks; - totals->totalspace += totalspace; - totals->freespace += freespace; - } + counters->nblocks = nblocks; + counters->nchunks = nchunks; + counters->freechunks = freechunks; + counters->totalspace = slab->memAllocated; + counters->freespace = freespace; + /* new memory is already sliced into chunks, so newspace is always 0 */ + counters->newspace = 0; } @@ -804,7 +792,7 @@ SlabCheck(MemoryContext context) } } - Assert(slab->nblocks * slab->blockSize == context->mem_allocated); + Assert(slab->nblocks * slab->blockSize == slab->memAllocated); } #endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h index c9f2bbcb367..798854da99d 100644 --- a/src/include/nodes/memnodes.h +++ b/src/include/nodes/memnodes.h @@ -25,15 +25,30 @@ * we might need more or different counters here. A possible API spec then * would be to print only nonzero counters, but for now we just summarize in * the format historically used by AllocSet. + * + * The 'newspace' counter represents memory that is allocated by malloc, but + * not touched yet. This can be significant for context types that malloc + * large blocks of memory in anticipation of chunks that have not yet been + * requested. */ typedef struct MemoryContextCounters { Size nblocks; /* Total number of malloc blocks */ + Size nchunks; /* Total number of chunks (used+free) */ Size freechunks; /* Total number of free chunks */ Size totalspace; /* Total bytes requested from malloc */ Size freespace; /* The unused portion of totalspace */ + Size newspace; /* Allocated but never held any chunks */ } MemoryContextCounters; +#define MCXT_COUNT_NBLOCKS (1 << 0) +#define MCXT_COUNT_NCHUNKS (1 << 1) +#define MCXT_COUNT_FREECHUNKS (1 << 2) +#define MCXT_COUNT_TOTALSPACE (1 << 3) +#define MCXT_COUNT_FREESPACE (1 << 4) +#define MCXT_COUNT_NEWSPACE (1 << 5) +#define MCXT_COUNT_ALL (~0) + /* * MemoryContext * A logical context in which memory allocations occur. @@ -51,9 +66,6 @@ typedef struct MemoryContextCounters * to the context struct rather than the struct type itself. */ -typedef void (*MemoryStatsPrintFunc) (MemoryContext context, void *passthru, - const char *stats_string); - typedef struct MemoryContextMethods { void *(*alloc) (MemoryContext context, Size size); @@ -64,9 +76,8 @@ typedef struct MemoryContextMethods void (*delete_context) (MemoryContext context); Size (*get_chunk_space) (MemoryContext context, void *pointer); bool (*is_empty) (MemoryContext context); - void (*stats) (MemoryContext context, - MemoryStatsPrintFunc printfunc, void *passthru, - MemoryContextCounters *totals); + void (*get_counters) (MemoryContext context, uint32 flags, + MemoryContextCounters *counters); #ifdef MEMORY_CONTEXT_CHECKING void (*check) (MemoryContext context); #endif @@ -79,7 +90,6 @@ 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 */ - Size 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 909bc2e9888..e787e43b3fb 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -82,7 +82,9 @@ extern void MemoryContextSetParent(MemoryContext context, extern Size GetMemoryChunkSpace(void *pointer); extern MemoryContext MemoryContextGetParent(MemoryContext context); extern bool MemoryContextIsEmpty(MemoryContext context); -extern Size MemoryContextMemAllocated(MemoryContext context, bool recurse); +extern void MemoryContextGetCounters(MemoryContext context, uint32 flags, + bool recurse, + MemoryContextCounters *counters); extern void MemoryContextStats(MemoryContext context); extern void MemoryContextStatsDetail(MemoryContext context, int max_children); extern void MemoryContextAllowInCriticalSection(MemoryContext context,