mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Recompute allocChunkLimit when reusing an AllocSet context
AllocSet contexts with different maxBlockSize values can have the same freeListIndex. When reusing a context from one of the context_freelists, we updated maxBlockSize, but we failed to adjust allocChunkLimit, even though that ordinarily depends on maxBlockSize. This could result in some allocations being put on an external block when they shouldn't have been, resulting in excessive malloc() calls, or if the allocChunkLimit was left set higher than it should be, allocations could have resulted in memory not being free()'d after pfree() due to the chunk having been put on a multi-chunk block (these would still later be cleaned up when the context is reset, so no memory leaks). The repercussions of this are only inefficient use of memory and perhaps some performance instability depending on which context happened to be top of the freelist when a new AllocSet context was created. To fix, we move the allocChunkLimit calculation code into a helper function and call that to reset the allocChunkLimit when reusing a context. Author: Chao Li <li.evan.chao@gmail.com> Reviewed-by: Donghang Lin <donghanglin@gmail.com> Reviewed-by: David Rowley <dgrowleyml@gmail.com> Discussion: https://postgr.es/m/1D54961E-6BEF-411A-BFAD-610C3B18D7E7@gmail.com Backpatch-through: 14
This commit is contained in:
@@ -315,6 +315,50 @@ AllocSetFreeIndex(Size size)
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* AllocSetFreeIndex -
|
||||
*
|
||||
* Compute and return the multi-chunk block chunk size limit for the
|
||||
* given maximum block size.
|
||||
* ----------
|
||||
*/
|
||||
static inline uint32
|
||||
AllocSetComputeChunkLimit(Size maxBlockSize)
|
||||
{
|
||||
uint32 allocChunkLimit;
|
||||
|
||||
/*
|
||||
* Compute the allocation chunk size limit for this context. It can't be
|
||||
* more than ALLOC_CHUNK_LIMIT because of the fixed number of freelists.
|
||||
* If maxBlockSize is small then requests exceeding the maxBlockSize, or
|
||||
* even a significant fraction of it, should be treated as large chunks
|
||||
* too. For the typical case of maxBlockSize a power of 2, the chunk size
|
||||
* limit will be at most 1/8th maxBlockSize, so that given a stream of
|
||||
* requests that are all the maximum chunk size we will waste at most
|
||||
* 1/8th of the allocated space.
|
||||
*
|
||||
* Also, allocChunkLimit must not exceed ALLOCSET_SEPARATE_THRESHOLD.
|
||||
*/
|
||||
StaticAssertStmt(ALLOC_CHUNK_LIMIT == ALLOCSET_SEPARATE_THRESHOLD,
|
||||
"ALLOC_CHUNK_LIMIT != ALLOCSET_SEPARATE_THRESHOLD");
|
||||
|
||||
/*
|
||||
* Determine the maximum size that a chunk can be before we allocate an
|
||||
* entire AllocBlock dedicated for that chunk. We set the absolute limit
|
||||
* of that size as ALLOC_CHUNK_LIMIT but we reduce it further so that we
|
||||
* can fit about ALLOC_CHUNK_FRACTION chunks this size on a maximally
|
||||
* sized block. (We opt to keep allocChunkLimit a power-of-2 value
|
||||
* primarily for legacy reasons rather than calculating it so that exactly
|
||||
* ALLOC_CHUNK_FRACTION chunks fit on a maximally sized block.)
|
||||
*/
|
||||
allocChunkLimit = ALLOC_CHUNK_LIMIT;
|
||||
while ((Size)(allocChunkLimit + ALLOC_CHUNKHDRSZ) >
|
||||
(Size)((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION))
|
||||
allocChunkLimit >>= 1;
|
||||
|
||||
return allocChunkLimit;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Public routines
|
||||
@@ -406,8 +450,18 @@ AllocSetContextCreateInternal(MemoryContext parent,
|
||||
freelist->first_free = (AllocSet) set->header.nextchild;
|
||||
freelist->num_free--;
|
||||
|
||||
/* Update its maxBlockSize; everything else should be OK */
|
||||
set->maxBlockSize = maxBlockSize;
|
||||
/* nextBlockSize should have been reset in AllocSetReset() */
|
||||
Assert(set->nextBlockSize == set->initBlockSize);
|
||||
|
||||
if (set->maxBlockSize != maxBlockSize)
|
||||
{
|
||||
/*
|
||||
* Update its maxBlockSize and allocChunkLimit; everything
|
||||
* else should be OK.
|
||||
*/
|
||||
set->maxBlockSize = maxBlockSize;
|
||||
set->allocChunkLimit = AllocSetComputeChunkLimit(maxBlockSize);
|
||||
}
|
||||
|
||||
/* Reinitialize its header, installing correct name and parent */
|
||||
MemoryContextCreate((MemoryContext) set,
|
||||
@@ -475,35 +529,7 @@ AllocSetContextCreateInternal(MemoryContext parent,
|
||||
set->maxBlockSize = maxBlockSize;
|
||||
set->nextBlockSize = initBlockSize;
|
||||
set->freeListIndex = freeListIndex;
|
||||
|
||||
/*
|
||||
* Compute the allocation chunk size limit for this context. It can't be
|
||||
* more than ALLOC_CHUNK_LIMIT because of the fixed number of freelists.
|
||||
* If maxBlockSize is small then requests exceeding the maxBlockSize, or
|
||||
* even a significant fraction of it, should be treated as large chunks
|
||||
* too. For the typical case of maxBlockSize a power of 2, the chunk size
|
||||
* limit will be at most 1/8th maxBlockSize, so that given a stream of
|
||||
* requests that are all the maximum chunk size we will waste at most
|
||||
* 1/8th of the allocated space.
|
||||
*
|
||||
* Also, allocChunkLimit must not exceed ALLOCSET_SEPARATE_THRESHOLD.
|
||||
*/
|
||||
StaticAssertStmt(ALLOC_CHUNK_LIMIT == ALLOCSET_SEPARATE_THRESHOLD,
|
||||
"ALLOC_CHUNK_LIMIT != ALLOCSET_SEPARATE_THRESHOLD");
|
||||
|
||||
/*
|
||||
* Determine the maximum size that a chunk can be before we allocate an
|
||||
* entire AllocBlock dedicated for that chunk. We set the absolute limit
|
||||
* of that size as ALLOC_CHUNK_LIMIT but we reduce it further so that we
|
||||
* can fit about ALLOC_CHUNK_FRACTION chunks this size on a maximally
|
||||
* sized block. (We opt to keep allocChunkLimit a power-of-2 value
|
||||
* primarily for legacy reasons rather than calculating it so that exactly
|
||||
* ALLOC_CHUNK_FRACTION chunks fit on a maximally sized block.)
|
||||
*/
|
||||
set->allocChunkLimit = ALLOC_CHUNK_LIMIT;
|
||||
while ((Size) (set->allocChunkLimit + ALLOC_CHUNKHDRSZ) >
|
||||
(Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION))
|
||||
set->allocChunkLimit >>= 1;
|
||||
set->allocChunkLimit = AllocSetComputeChunkLimit(maxBlockSize);
|
||||
|
||||
/* Finally, do the type-independent part of context creation */
|
||||
MemoryContextCreate((MemoryContext) set,
|
||||
|
||||
Reference in New Issue
Block a user