Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c6e0fe1

Browse files
committed
Improve performance of and reduce overheads of memory management
Whenever we palloc a chunk of memory, traditionally, we prefix the returned pointer with a pointer to the memory context to which the chunk belongs. This is required so that we're able to easily determine the owning context when performing operations such as pfree() and repalloc(). For the AllocSet context, prior to this commit we additionally prefixed the pointer to the owning context with the size of the chunk. This made the header 16 bytes in size. This 16-byte overhead was required for all AllocSet allocations regardless of the allocation size. For the generation context, the problem was worse; in addition to the pointer to the owning context and chunk size, we also stored a pointer to the owning block so that we could track the number of freed chunks on a block. The slab allocator had a 16-byte chunk header. The changes being made here reduce the chunk header size down to just 8 bytes for all 3 of our memory context types. For small to medium sized allocations, this significantly increases the number of chunks that we can fit on a given block which results in much more efficient use of memory. Additionally, this commit completely changes the rule that pointers to palloc'd memory must be directly prefixed by a pointer to the owning memory context and instead, we now insist that they're directly prefixed by an 8-byte value where the least significant 3-bits are set to a value to indicate which type of memory context the pointer belongs to. Using those 3 bits as an index (known as MemoryContextMethodID) to a new array which stores the methods for each memory context type, we're now able to pass the pointer given to functions such as pfree() and repalloc() to the function specific to that context implementation to allow them to devise their own methods of finding the memory context which owns the given allocated chunk of memory. The reason we're able to reduce the chunk header down to just 8 bytes is because of the way we make use of the remaining 61 bits of the required 8-byte chunk header. Here we also implement a general-purpose MemoryChunk struct which makes use of those 61 remaining bits to allow the storage of a 30-bit value which the MemoryContext is free to use as it pleases, and also the number of bytes which must be subtracted from the chunk to get a reference to the block that the chunk is stored on (also 30 bits). The 1 additional remaining bit is to denote if the chunk is an "external" chunk or not. External here means that the chunk header does not store the 30-bit value or the block offset. The MemoryContext can use these external chunks at any time, but must use them if any of the two 30-bit fields are not large enough for the value(s) that need to be stored in them. When the chunk is marked as external, it is up to the MemoryContext to devise its own means to determine the block offset. Using 3-bits for the MemoryContextMethodID does mean we're limiting ourselves to only having a maximum of 8 different memory context types. We could reduce the bit space for the 30-bit value a little to make way for more than 3 bits, but it seems like it might be better to do that only if we ever need more than 8 context types. This would only be a problem if some future memory context type which does not use MemoryChunk really couldn't give up any of the 61 remaining bits in the chunk header. With this MemoryChunk, each of our 3 memory context types can quickly obtain a reference to the block any given chunk is located on. AllocSet is able to find the context to which the chunk is owned, by first obtaining a reference to the block by subtracting the block offset as is stored in the 'hdrmask' field and then referencing the block's 'aset' field. The Generation context uses the same method, but GenerationBlock did not have a field pointing back to the owning context, so one is added by this commit. In aset.c and generation.c, all allocations larger than allocChunkLimit are stored on dedicated blocks. When there's just a single chunk on a block like this, it's easy to find the block from the chunk, we just subtract the size of the block header from the chunk pointer. The size of these chunks is also known as we store the endptr on the block, so we can just subtract the pointer to the allocated memory from that. Because we can easily find the owning block and the size of the chunk for these dedicated blocks, we just always use external chunks for allocation sizes larger than allocChunkLimit. For generation.c, this sidesteps the problem of non-external MemoryChunks being unable to represent chunk sizes >= 1GB. This is less of a problem for aset.c as we store the free list index in the MemoryChunk's spare 30-bit field (the value of which will never be close to using all 30-bits). We can easily reverse engineer the chunk size from this when needed. Storing this saves AllocSetFree() from having to make a call to AllocSetFreeIndex() to determine which free list to put the newly freed chunk on. For the slab allocator, this commit adds a new restriction that slab chunks cannot be >= 1GB in size. If there happened to be any users of slab.c which used chunk sizes this large, they really should be using AllocSet instead. Here we also add a restriction that normal non-dedicated blocks cannot be 1GB or larger. It's now not possible to pass a 'maxBlockSize' >= 1GB during the creation of an AllocSet or Generation context. Allocations can still be larger than 1GB, it's just these will always be on dedicated blocks (which do not have the 1GB restriction). Author: Andres Freund, David Rowley Discussion: https://postgr.es/m/CAApHDvpjauCRXcgcaL6+e3eqecEHoeRm9D-kcbuvBitgPnW=vw@mail.gmail.com
1 parent d2169c9 commit c6e0fe1

File tree

10 files changed

+1056
-618
lines changed

10 files changed

+1056
-618
lines changed

src/backend/utils/mmgr/README

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -384,35 +384,55 @@ MemoryContext like the parent and child contexts, and the name of the
384384
context.
385385

386386
This is essentially an abstract superclass, and the behavior is
387-
determined by the "methods" pointer is its virtual function table
388-
(struct MemoryContextMethods). Specific memory context types will use
389-
derived structs having these fields as their first fields. All the
387+
determined by the "methods" pointer which references which set of
388+
MemoryContextMethods are to be used. Specific memory context types will
389+
use derived structs having these fields as their first fields. All the
390390
contexts of a specific type will have methods pointers that point to
391-
the same static table of function pointers.
391+
the corresponding element in the mcxt_methods[] array as defined in mcxt.c.
392392

393393
While operations like allocating from and resetting a context take the
394394
relevant MemoryContext as a parameter, operations like free and
395395
realloc are trickier. To make those work, we require all memory
396396
context types to produce allocated chunks that are immediately,
397-
without any padding, preceded by a pointer to the corresponding
398-
MemoryContext.
397+
without any padding, preceded by a uint64 value of which the least
398+
significant 3 bits are set to the owning context's MemoryContextMethodID.
399+
This allows the code to determine the correct MemoryContextMethods to
400+
use by looking up the mcxt_methods[] array using the 3 bits as an index
401+
into that array.
399402

400403
If a type of allocator needs additional information about its chunks,
401404
like e.g. the size of the allocation, that information can in turn
402-
precede the MemoryContext. This means the only overhead implied by
403-
the memory context mechanism is a pointer to its context, so we're not
404-
constraining context-type designers very much.
405-
406-
Given this, routines like pfree determine their corresponding context
407-
with an operation like (although that is usually encapsulated in
408-
GetMemoryChunkContext())
409-
410-
MemoryContext context = *(MemoryContext*) (((char *) pointer) - sizeof(void *));
411-
412-
and then invoke the corresponding method for the context
413-
414-
context->methods->free_p(pointer);
415-
405+
either be encoded into the remaining 61 bits of the preceding uint64 value
406+
or if more space is required, additional values may be stored directly prior
407+
to the uint64 value. It is up to the context implementation to manage this.
408+
409+
Given this, routines like pfree can determine which set of
410+
MemoryContextMethods to call the free_p function for by calling
411+
GetMemoryChunkMethodID() and finding the corresponding MemoryContextMethods
412+
in the mcxt_methods[] array. For convenience, the MCXT_METHOD() macro is
413+
provided, making the code as simple as:
414+
415+
void
416+
pfree(void *pointer)
417+
{
418+
MCXT_METHOD(pointer, free_p)(pointer);
419+
}
420+
421+
All of the current memory contexts make use of the MemoryChunk header type
422+
which is defined in memutils_memorychunk.h. This suits all of the existing
423+
context types well as it makes use of the remaining 61-bits of the uint64
424+
header to efficiently encode the size of the chunk of memory (or freelist
425+
index, in the case of aset.c) and the number of bytes which must be subtracted
426+
from the chunk in order to obtain a reference to the block that the chunk
427+
belongs to. 30 bits are used for each of these. If more than 30 bits are
428+
required then the memory context must manage that itself. This can be done by
429+
calling the MemoryChunkSetHdrMaskExternal() function on the given chunk.
430+
Currently, each memory context type stores large allocations on dedicated
431+
blocks (which always contain only a single chunk). For these, finding the
432+
block is simple as we know that the chunk must be the first on the given
433+
block, so the block is always at a fixed offset to the chunk. For these,
434+
finding the size of the chunk is also simple as the block always stores an
435+
endptr which we can use to calculate the size of the chunk.
416436

417437
More Control Over aset.c Behavior
418438
---------------------------------

0 commit comments

Comments
 (0)