Add transparent block-level memory accounting
Adds accounting of memory allocated in a memory context. Compared to various ad hoc solutions, the main advantage is that the accounting is transparent and does not require direct control over allocations (this matters for use cases where the allocations happen in user code, like for example aggregate states allocated in a transition functions). To reduce overhead, the accounting happens at the block level (not for individual chunks) and only the context immediately owning the block is updated. When inquiring about amount of memory allocated in a context, we have to recursively walk all children contexts. This "lazy" accounting works well for cases with relatively small number of contexts in the relevant subtree and/or with infrequent inquiries. Author: Jeff Davis Reivewed-by: Tomas Vondra, Melanie Plageman, Soumyadeep Chakraborty Discussion: https://www.postgresql.org/message-id/flat/027a129b8525601c6a680d27ce3a7172dab61aab.camel@j-davis.com
This commit is contained in:
parent
36d22dd95b
commit
5dd7fc1519
@ -23,6 +23,10 @@ The basic operations on a memory context are:
|
|||||||
* reset a context (free all memory allocated in the context, but not the
|
* reset a context (free all memory allocated in the context, but not the
|
||||||
context object itself)
|
context object itself)
|
||||||
|
|
||||||
|
* inquire about the total amount of memory allocated to the context
|
||||||
|
(the raw memory from which the context allocates chunks; not the
|
||||||
|
chunks themselves)
|
||||||
|
|
||||||
Given a chunk of memory previously allocated from a context, one can
|
Given a chunk of memory previously allocated from a context, one can
|
||||||
free it or reallocate it larger or smaller (corresponding to standard C
|
free it or reallocate it larger or smaller (corresponding to standard C
|
||||||
library's free() and realloc() routines). These operations return memory
|
library's free() and realloc() routines). These operations return memory
|
||||||
@ -452,3 +456,33 @@ returns the memory when reset/deleted).
|
|||||||
|
|
||||||
These memory contexts were initially developed for ReorderBuffer, but
|
These memory contexts were initially developed for ReorderBuffer, but
|
||||||
may be useful elsewhere as long as the allocation patterns match.
|
may be useful elsewhere as long as the allocation patterns match.
|
||||||
|
|
||||||
|
|
||||||
|
Memory Accounting
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
One of the basic memory context operations is determining the amount of
|
||||||
|
memory used in the context (and it's children). We have multiple places
|
||||||
|
that implement their own ad hoc memory accounting, and this is meant to
|
||||||
|
provide a unified approach. Ad hoc accounting solutions work for places
|
||||||
|
with tight control over the allocations or when it's easy to determine
|
||||||
|
sizes of allocated chunks (e.g. places that only work with tuples).
|
||||||
|
|
||||||
|
The accounting built into the memory contexts is transparent and works
|
||||||
|
transparently for all allocations as long as they end up in the right
|
||||||
|
memory context subtree.
|
||||||
|
|
||||||
|
Consider for example aggregate functions - the aggregate state is often
|
||||||
|
represented by an arbitrary structure, allocated from the transition
|
||||||
|
function, so the ad hoc accounting is unlikely to work. The built-in
|
||||||
|
accounting will however handle such cases just fine.
|
||||||
|
|
||||||
|
To minimize overhead, the accounting is done at the block level, not for
|
||||||
|
individual allocation chunks.
|
||||||
|
|
||||||
|
The accounting is lazy - after a block is allocated (or freed), only the
|
||||||
|
context owning that block is updated. This means that when inquiring
|
||||||
|
about the memory usage in a given context, we have to walk all children
|
||||||
|
contexts recursively. This means the memory accounting is not intended
|
||||||
|
for cases with too many memory contexts (in the relevant subtree).
|
||||||
|
|
||||||
|
@ -458,6 +458,9 @@ AllocSetContextCreateInternal(MemoryContext parent,
|
|||||||
parent,
|
parent,
|
||||||
name);
|
name);
|
||||||
|
|
||||||
|
((MemoryContext) set)->mem_allocated =
|
||||||
|
set->keeper->endptr - ((char *) set);
|
||||||
|
|
||||||
return (MemoryContext) set;
|
return (MemoryContext) set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -546,6 +549,8 @@ AllocSetContextCreateInternal(MemoryContext parent,
|
|||||||
parent,
|
parent,
|
||||||
name);
|
name);
|
||||||
|
|
||||||
|
((MemoryContext) set)->mem_allocated = firstBlockSize;
|
||||||
|
|
||||||
return (MemoryContext) set;
|
return (MemoryContext) set;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,6 +571,7 @@ AllocSetReset(MemoryContext context)
|
|||||||
{
|
{
|
||||||
AllocSet set = (AllocSet) context;
|
AllocSet set = (AllocSet) context;
|
||||||
AllocBlock block;
|
AllocBlock block;
|
||||||
|
Size keepersize = set->keeper->endptr - ((char *) set);
|
||||||
|
|
||||||
AssertArg(AllocSetIsValid(set));
|
AssertArg(AllocSetIsValid(set));
|
||||||
|
|
||||||
@ -604,6 +610,8 @@ AllocSetReset(MemoryContext context)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Normal case, release the block */
|
/* Normal case, release the block */
|
||||||
|
context->mem_allocated -= block->endptr - ((char*) block);
|
||||||
|
|
||||||
#ifdef CLOBBER_FREED_MEMORY
|
#ifdef CLOBBER_FREED_MEMORY
|
||||||
wipe_mem(block, block->freeptr - ((char *) block));
|
wipe_mem(block, block->freeptr - ((char *) block));
|
||||||
#endif
|
#endif
|
||||||
@ -612,6 +620,8 @@ AllocSetReset(MemoryContext context)
|
|||||||
block = next;
|
block = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert(context->mem_allocated == keepersize);
|
||||||
|
|
||||||
/* Reset block size allocation sequence, too */
|
/* Reset block size allocation sequence, too */
|
||||||
set->nextBlockSize = set->initBlockSize;
|
set->nextBlockSize = set->initBlockSize;
|
||||||
}
|
}
|
||||||
@ -628,6 +638,7 @@ AllocSetDelete(MemoryContext context)
|
|||||||
{
|
{
|
||||||
AllocSet set = (AllocSet) context;
|
AllocSet set = (AllocSet) context;
|
||||||
AllocBlock block = set->blocks;
|
AllocBlock block = set->blocks;
|
||||||
|
Size keepersize = set->keeper->endptr - ((char *) set);
|
||||||
|
|
||||||
AssertArg(AllocSetIsValid(set));
|
AssertArg(AllocSetIsValid(set));
|
||||||
|
|
||||||
@ -683,6 +694,9 @@ AllocSetDelete(MemoryContext context)
|
|||||||
{
|
{
|
||||||
AllocBlock next = block->next;
|
AllocBlock next = block->next;
|
||||||
|
|
||||||
|
if (block != set->keeper)
|
||||||
|
context->mem_allocated -= block->endptr - ((char *) block);
|
||||||
|
|
||||||
#ifdef CLOBBER_FREED_MEMORY
|
#ifdef CLOBBER_FREED_MEMORY
|
||||||
wipe_mem(block, block->freeptr - ((char *) block));
|
wipe_mem(block, block->freeptr - ((char *) block));
|
||||||
#endif
|
#endif
|
||||||
@ -693,6 +707,8 @@ AllocSetDelete(MemoryContext context)
|
|||||||
block = next;
|
block = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert(context->mem_allocated == keepersize);
|
||||||
|
|
||||||
/* Finally, free the context header, including the keeper block */
|
/* Finally, free the context header, including the keeper block */
|
||||||
free(set);
|
free(set);
|
||||||
}
|
}
|
||||||
@ -733,6 +749,9 @@ AllocSetAlloc(MemoryContext context, Size size)
|
|||||||
block = (AllocBlock) malloc(blksize);
|
block = (AllocBlock) malloc(blksize);
|
||||||
if (block == NULL)
|
if (block == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
context->mem_allocated += blksize;
|
||||||
|
|
||||||
block->aset = set;
|
block->aset = set;
|
||||||
block->freeptr = block->endptr = ((char *) block) + blksize;
|
block->freeptr = block->endptr = ((char *) block) + blksize;
|
||||||
|
|
||||||
@ -928,6 +947,8 @@ AllocSetAlloc(MemoryContext context, Size size)
|
|||||||
if (block == NULL)
|
if (block == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
context->mem_allocated += blksize;
|
||||||
|
|
||||||
block->aset = set;
|
block->aset = set;
|
||||||
block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
|
block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
|
||||||
block->endptr = ((char *) block) + blksize;
|
block->endptr = ((char *) block) + blksize;
|
||||||
@ -1028,6 +1049,9 @@ AllocSetFree(MemoryContext context, void *pointer)
|
|||||||
set->blocks = block->next;
|
set->blocks = block->next;
|
||||||
if (block->next)
|
if (block->next)
|
||||||
block->next->prev = block->prev;
|
block->next->prev = block->prev;
|
||||||
|
|
||||||
|
context->mem_allocated -= block->endptr - ((char*) block);
|
||||||
|
|
||||||
#ifdef CLOBBER_FREED_MEMORY
|
#ifdef CLOBBER_FREED_MEMORY
|
||||||
wipe_mem(block, block->freeptr - ((char *) block));
|
wipe_mem(block, block->freeptr - ((char *) block));
|
||||||
#endif
|
#endif
|
||||||
@ -1144,6 +1168,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
|
|||||||
AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ);
|
AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ);
|
||||||
Size chksize;
|
Size chksize;
|
||||||
Size blksize;
|
Size blksize;
|
||||||
|
Size oldblksize;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try to verify that we have a sane block pointer: it should
|
* Try to verify that we have a sane block pointer: it should
|
||||||
@ -1159,6 +1184,8 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
|
|||||||
/* Do the realloc */
|
/* Do the realloc */
|
||||||
chksize = MAXALIGN(size);
|
chksize = MAXALIGN(size);
|
||||||
blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
|
blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
|
||||||
|
oldblksize = block->endptr - ((char *)block);
|
||||||
|
|
||||||
block = (AllocBlock) realloc(block, blksize);
|
block = (AllocBlock) realloc(block, blksize);
|
||||||
if (block == NULL)
|
if (block == NULL)
|
||||||
{
|
{
|
||||||
@ -1166,6 +1193,9 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
|
|||||||
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
|
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context->mem_allocated += blksize - oldblksize;
|
||||||
|
|
||||||
block->freeptr = block->endptr = ((char *) block) + blksize;
|
block->freeptr = block->endptr = ((char *) block) + blksize;
|
||||||
|
|
||||||
/* Update pointers since block has likely been moved */
|
/* Update pointers since block has likely been moved */
|
||||||
@ -1383,6 +1413,7 @@ AllocSetCheck(MemoryContext context)
|
|||||||
const char *name = set->header.name;
|
const char *name = set->header.name;
|
||||||
AllocBlock prevblock;
|
AllocBlock prevblock;
|
||||||
AllocBlock block;
|
AllocBlock block;
|
||||||
|
int64 total_allocated = 0;
|
||||||
|
|
||||||
for (prevblock = NULL, block = set->blocks;
|
for (prevblock = NULL, block = set->blocks;
|
||||||
block != NULL;
|
block != NULL;
|
||||||
@ -1393,6 +1424,11 @@ AllocSetCheck(MemoryContext context)
|
|||||||
long blk_data = 0;
|
long blk_data = 0;
|
||||||
long nchunks = 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
|
* Empty block - empty can be keeper-block only
|
||||||
*/
|
*/
|
||||||
@ -1479,6 +1515,8 @@ AllocSetCheck(MemoryContext context)
|
|||||||
elog(WARNING, "problem in alloc set %s: found inconsistent memory block %p",
|
elog(WARNING, "problem in alloc set %s: found inconsistent memory block %p",
|
||||||
name, block);
|
name, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert(total_allocated == context->mem_allocated);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* MEMORY_CONTEXT_CHECKING */
|
#endif /* MEMORY_CONTEXT_CHECKING */
|
||||||
|
@ -297,6 +297,8 @@ GenerationReset(MemoryContext context)
|
|||||||
|
|
||||||
dlist_delete(miter.cur);
|
dlist_delete(miter.cur);
|
||||||
|
|
||||||
|
context->mem_allocated -= block->blksize;
|
||||||
|
|
||||||
#ifdef CLOBBER_FREED_MEMORY
|
#ifdef CLOBBER_FREED_MEMORY
|
||||||
wipe_mem(block, block->blksize);
|
wipe_mem(block, block->blksize);
|
||||||
#endif
|
#endif
|
||||||
@ -352,6 +354,8 @@ GenerationAlloc(MemoryContext context, Size size)
|
|||||||
if (block == NULL)
|
if (block == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
context->mem_allocated += blksize;
|
||||||
|
|
||||||
/* block with a single (used) chunk */
|
/* block with a single (used) chunk */
|
||||||
block->blksize = blksize;
|
block->blksize = blksize;
|
||||||
block->nchunks = 1;
|
block->nchunks = 1;
|
||||||
@ -407,6 +411,8 @@ GenerationAlloc(MemoryContext context, Size size)
|
|||||||
if (block == NULL)
|
if (block == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
context->mem_allocated += blksize;
|
||||||
|
|
||||||
block->blksize = blksize;
|
block->blksize = blksize;
|
||||||
block->nchunks = 0;
|
block->nchunks = 0;
|
||||||
block->nfree = 0;
|
block->nfree = 0;
|
||||||
@ -522,6 +528,7 @@ GenerationFree(MemoryContext context, void *pointer)
|
|||||||
if (set->block == block)
|
if (set->block == block)
|
||||||
set->block = NULL;
|
set->block = NULL;
|
||||||
|
|
||||||
|
context->mem_allocated -= block->blksize;
|
||||||
free(block);
|
free(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,6 +753,7 @@ GenerationCheck(MemoryContext context)
|
|||||||
GenerationContext *gen = (GenerationContext *) context;
|
GenerationContext *gen = (GenerationContext *) context;
|
||||||
const char *name = context->name;
|
const char *name = context->name;
|
||||||
dlist_iter iter;
|
dlist_iter iter;
|
||||||
|
int64 total_allocated = 0;
|
||||||
|
|
||||||
/* walk all blocks in this context */
|
/* walk all blocks in this context */
|
||||||
dlist_foreach(iter, &gen->blocks)
|
dlist_foreach(iter, &gen->blocks)
|
||||||
@ -755,6 +763,8 @@ GenerationCheck(MemoryContext context)
|
|||||||
nchunks;
|
nchunks;
|
||||||
char *ptr;
|
char *ptr;
|
||||||
|
|
||||||
|
total_allocated += block->blksize;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* nfree > nchunks is surely wrong, and we don't expect to see
|
* nfree > nchunks is surely wrong, and we don't expect to see
|
||||||
* equality either, because such a block should have gotten freed.
|
* equality either, because such a block should have gotten freed.
|
||||||
@ -833,6 +843,8 @@ GenerationCheck(MemoryContext context)
|
|||||||
elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d",
|
elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d",
|
||||||
name, nfree, block, block->nfree);
|
name, nfree, block, block->nfree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert(total_allocated == context->mem_allocated);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* MEMORY_CONTEXT_CHECKING */
|
#endif /* MEMORY_CONTEXT_CHECKING */
|
||||||
|
@ -462,6 +462,30 @@ MemoryContextIsEmpty(MemoryContext context)
|
|||||||
return context->methods->is_empty(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
|
* MemoryContextStats
|
||||||
* Print statistics about the named context and all its descendants.
|
* Print statistics about the named context and all its descendants.
|
||||||
@ -736,6 +760,7 @@ MemoryContextCreate(MemoryContext node,
|
|||||||
node->methods = methods;
|
node->methods = methods;
|
||||||
node->parent = parent;
|
node->parent = parent;
|
||||||
node->firstchild = NULL;
|
node->firstchild = NULL;
|
||||||
|
node->mem_allocated = 0;
|
||||||
node->prevchild = NULL;
|
node->prevchild = NULL;
|
||||||
node->name = name;
|
node->name = name;
|
||||||
node->ident = NULL;
|
node->ident = NULL;
|
||||||
|
@ -305,12 +305,14 @@ SlabReset(MemoryContext context)
|
|||||||
#endif
|
#endif
|
||||||
free(block);
|
free(block);
|
||||||
slab->nblocks--;
|
slab->nblocks--;
|
||||||
|
context->mem_allocated -= slab->blockSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slab->minFreeChunks = 0;
|
slab->minFreeChunks = 0;
|
||||||
|
|
||||||
Assert(slab->nblocks == 0);
|
Assert(slab->nblocks == 0);
|
||||||
|
Assert(context->mem_allocated == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -388,6 +390,7 @@ SlabAlloc(MemoryContext context, Size size)
|
|||||||
|
|
||||||
slab->minFreeChunks = slab->chunksPerBlock;
|
slab->minFreeChunks = slab->chunksPerBlock;
|
||||||
slab->nblocks += 1;
|
slab->nblocks += 1;
|
||||||
|
context->mem_allocated += slab->blockSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* grab the block from the freelist (even the new block is there) */
|
/* grab the block from the freelist (even the new block is there) */
|
||||||
@ -480,6 +483,9 @@ SlabAlloc(MemoryContext context, Size size)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
SlabAllocInfo(slab, chunk);
|
SlabAllocInfo(slab, chunk);
|
||||||
|
|
||||||
|
Assert(slab->nblocks * slab->blockSize == context->mem_allocated);
|
||||||
|
|
||||||
return SlabChunkGetPointer(chunk);
|
return SlabChunkGetPointer(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,11 +561,13 @@ SlabFree(MemoryContext context, void *pointer)
|
|||||||
{
|
{
|
||||||
free(block);
|
free(block);
|
||||||
slab->nblocks--;
|
slab->nblocks--;
|
||||||
|
context->mem_allocated -= slab->blockSize;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
dlist_push_head(&slab->freelist[block->nfree], &block->node);
|
dlist_push_head(&slab->freelist[block->nfree], &block->node);
|
||||||
|
|
||||||
Assert(slab->nblocks >= 0);
|
Assert(slab->nblocks >= 0);
|
||||||
|
Assert(slab->nblocks * slab->blockSize == context->mem_allocated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -782,6 +790,8 @@ SlabCheck(MemoryContext context)
|
|||||||
name, block->nfree, block, nfree);
|
name, block->nfree, block, nfree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert(slab->nblocks * slab->blockSize == context->mem_allocated);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* MEMORY_CONTEXT_CHECKING */
|
#endif /* MEMORY_CONTEXT_CHECKING */
|
||||||
|
@ -79,6 +79,7 @@ typedef struct MemoryContextData
|
|||||||
/* these two fields are placed here to minimize alignment wastage: */
|
/* these two fields are placed here to minimize alignment wastage: */
|
||||||
bool isReset; /* T = no space alloced since last reset */
|
bool isReset; /* T = no space alloced since last reset */
|
||||||
bool allowInCritSection; /* allow palloc in critical section */
|
bool allowInCritSection; /* allow palloc in critical section */
|
||||||
|
int64 mem_allocated; /* track memory allocated for this context */
|
||||||
const MemoryContextMethods *methods; /* virtual function table */
|
const MemoryContextMethods *methods; /* virtual function table */
|
||||||
MemoryContext parent; /* NULL if no parent (toplevel context) */
|
MemoryContext parent; /* NULL if no parent (toplevel context) */
|
||||||
MemoryContext firstchild; /* head of linked list of children */
|
MemoryContext firstchild; /* head of linked list of children */
|
||||||
|
@ -82,6 +82,7 @@ extern void MemoryContextSetParent(MemoryContext context,
|
|||||||
extern Size GetMemoryChunkSpace(void *pointer);
|
extern Size GetMemoryChunkSpace(void *pointer);
|
||||||
extern MemoryContext MemoryContextGetParent(MemoryContext context);
|
extern MemoryContext MemoryContextGetParent(MemoryContext context);
|
||||||
extern bool MemoryContextIsEmpty(MemoryContext context);
|
extern bool MemoryContextIsEmpty(MemoryContext context);
|
||||||
|
extern int64 MemoryContextMemAllocated(MemoryContext context, bool recurse);
|
||||||
extern void MemoryContextStats(MemoryContext context);
|
extern void MemoryContextStats(MemoryContext context);
|
||||||
extern void MemoryContextStatsDetail(MemoryContext context, int max_children);
|
extern void MemoryContextStatsDetail(MemoryContext context, int max_children);
|
||||||
extern void MemoryContextAllowInCriticalSection(MemoryContext context,
|
extern void MemoryContextAllowInCriticalSection(MemoryContext context,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user