diff options
author | David Rowley <drowley@postgresql.org> | 2022-12-22 13:32:05 +1300 |
---|---|---|
committer | David Rowley <drowley@postgresql.org> | 2022-12-22 13:32:05 +1300 |
commit | 439f61757f054109f9ee5415530a2744f7e5cb7a (patch) | |
tree | 984945862f40bc5b4f49c8409e3ab67e9f300bf5 /src/backend/utils/mmgr/alignedalloc.c | |
parent | 701c881f782b93ee29587112390bd3bfe035e78d (diff) | |
download | postgresql-439f61757f054109f9ee5415530a2744f7e5cb7a.tar.gz postgresql-439f61757f054109f9ee5415530a2744f7e5cb7a.zip |
Add palloc_aligned() to allow aligned memory allocations
This introduces palloc_aligned() and MemoryContextAllocAligned() which
allow callers to obtain memory which is allocated to the given size and
also aligned to the specified alignment boundary. The alignment
boundaries may be any power-of-2 value. Currently, the alignment is
capped at 2^26, however, we don't expect values anything like that large.
The primary expected use case is to align allocations to perhaps CPU
cache line size or to maybe I/O page size. Certain use cases can benefit
from having aligned memory by either having better performance or more
predictable performance.
The alignment is achieved by requesting 'alignto' additional bytes from
the underlying allocator function and then aligning the address that is
returned to the requested alignment. This obviously does waste some
memory, so alignments should be kept as small as what is required.
It's also important to note that these alignment bytes eat into the
maximum allocation size. So something like:
palloc_aligned(MaxAllocSize, 64, 0);
will not work as we cannot request MaxAllocSize + 64 bytes.
Additionally, because we're just requesting the requested size plus the
alignment requirements from the given MemoryContext, if that context is
the Slab allocator, then since slab can only provide chunks of the size
that's specified when the slab context is created, then this is not going
to work. Slab will generate an error to indicate that the requested size
is not supported.
The alignment that is requested in palloc_aligned() is stored along with
the allocated memory. This allows the alignment to remain intact through
repalloc() calls.
Author: Andres Freund, David Rowley
Reviewed-by: Maxim Orlov, Andres Freund, John Naylor
Discussion: https://postgr.es/m/CAApHDvpxLPUMV1mhxs6g7GNwCP6Cs6hfnYQL5ffJQTuFAuxt8A%40mail.gmail.com
Diffstat (limited to 'src/backend/utils/mmgr/alignedalloc.c')
-rw-r--r-- | src/backend/utils/mmgr/alignedalloc.c | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/src/backend/utils/mmgr/alignedalloc.c b/src/backend/utils/mmgr/alignedalloc.c new file mode 100644 index 00000000000..62f9dbfe89a --- /dev/null +++ b/src/backend/utils/mmgr/alignedalloc.c @@ -0,0 +1,132 @@ +/*------------------------------------------------------------------------- + * + * alignedalloc.c + * Allocator functions to implement palloc_aligned + * + * This is not a fully-fledged MemoryContext type as there is no means to + * create a MemoryContext of this type. The code here only serves to allow + * operations such as pfree() and repalloc() to work correctly on a memory + * chunk that was allocated by palloc_aligned(). + * + * Portions Copyright (c) 2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/mmgr/alignedalloc.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/memdebug.h" +#include "utils/memutils_memorychunk.h" + +/* + * AlignedAllocFree +* Frees allocated memory; memory is removed from its owning context. +*/ +void +AlignedAllocFree(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + void *unaligned; + + Assert(!MemoryChunkIsExternal(chunk)); + + /* obtain the original (unaligned) allocated pointer */ + unaligned = MemoryChunkGetBlock(chunk); + +#ifdef MEMORY_CONTEXT_CHECKING + /* Test for someone scribbling on unused space in chunk */ + if (!sentinel_ok(pointer, chunk->requested_size)) + elog(WARNING, "detected write past chunk end in %s %p", + GetMemoryChunkContext(unaligned)->name, chunk); +#endif + + pfree(unaligned); +} + +/* + * AlignedAllocRealloc + * Change the allocated size of a chunk and return possibly a different + * pointer to a memory address aligned to the same boundary as the + * originally requested alignment. The contents of 'pointer' will be + * copied into the returned pointer up until 'size'. Any additional + * memory will be uninitialized. + */ +void * +AlignedAllocRealloc(void *pointer, Size size) +{ + MemoryChunk *redirchunk = PointerGetMemoryChunk(pointer); + Size alignto = MemoryChunkGetValue(redirchunk); + void *unaligned = MemoryChunkGetBlock(redirchunk); + MemoryContext ctx; + Size old_size; + void *newptr; + + /* sanity check this is a power of 2 value */ + Assert((alignto & (alignto - 1)) == 0); + + /* + * Determine the size of the original allocation. We can't determine this + * exactly as GetMemoryChunkSpace() returns the total space used for the + * allocation, which for contexts like aset includes rounding up to the + * next power of 2. However, this value is just used to memcpy() the old + * data into the new allocation, so we only need to concern ourselves with + * not reading beyond the end of the original allocation's memory. The + * drawback here is that we may copy more bytes than we need to, which + * only amounts to wasted effort. We can safely subtract the extra bytes + * that we requested to allow us to align the pointer. We must also + * subtract the space for the unaligned pointer's MemoryChunk since + * GetMemoryChunkSpace should have included that. This does assume that + * all context types use MemoryChunk as a chunk header. + */ + old_size = GetMemoryChunkSpace(unaligned) - + PallocAlignedExtraBytes(alignto) - sizeof(MemoryChunk); + +#ifdef MEMORY_CONTEXT_CHECKING + /* check that GetMemoryChunkSpace returned something realistic */ + Assert(old_size >= redirchunk->requested_size); +#endif + + ctx = GetMemoryChunkContext(unaligned); + newptr = MemoryContextAllocAligned(ctx, size, alignto, 0); + + /* + * We may memcpy beyond the end of the original allocation request size, + * so we must mark the entire allocation as defined. + */ + VALGRIND_MAKE_MEM_DEFINED(pointer, old_size); + memcpy(newptr, pointer, Min(size, old_size)); + pfree(unaligned); + + return newptr; +} + +/* + * AlignedAllocGetChunkContext + * Return the MemoryContext that 'pointer' belongs to. + */ +MemoryContext +AlignedAllocGetChunkContext(void *pointer) +{ + MemoryChunk *chunk = PointerGetMemoryChunk(pointer); + + Assert(!MemoryChunkIsExternal(chunk)); + + return GetMemoryChunkContext(MemoryChunkGetBlock(chunk)); +} + +/* + * AlignedAllocGetChunkSpace + * Given a currently-allocated chunk, determine the total space + * it occupies (including all memory-allocation overhead). + */ +Size +AlignedAllocGetChunkSpace(void *pointer) +{ + MemoryChunk *redirchunk = PointerGetMemoryChunk(pointer); + void *unaligned = MemoryChunkGetBlock(redirchunk); + + return GetMemoryChunkSpace(unaligned); +} |