On Tue, 5 Mar 2024 at 15:42, David Rowley <dgrowle...@gmail.com> wrote: > The query I ran was: > > select chksz,mtype,pg_allocate_memory_test_reset(chksz, > 1024*1024,1024*1024*1024, mtype) from (values(8),(16),(32),(64)) > sizes(chksz),(values('aset'),('generation'),('slab'),('bump')) > cxt(mtype) order by mtype,chksz;
Andres and I were discussing this patch offlist in the context of "should we have bump". Andres wonders if it would be better to have a function such as palloc_nofree() (we didn't actually discuss the name), which for aset, would forego rounding up to the next power of 2 and not bother checking the freelist and only have a chunk header for MEMORY_CONTEXT_CHECKING builds. For non-MEMORY_CONTEXT_CHECKING builds, the chunk header could be set to some other context type such as one of the unused ones or perhaps a dedicated new one that does something along the lines of BogusFree() which raises an ERROR if anything tries to pfree or repalloc it. An advantage of having this instead of bump would be that it could be used for things such as the parser, where we make a possibly large series of small allocations and never free them again. Andres ask me to run some benchmarks to mock up AllocSetAlloc() to have it not check the freelist to see how the performance of it compares to BumpAlloc(). I did this in the attached 2 patches. The 0001 patch just #ifdefs that part of AllocSetAlloc out, however properly implementing this is more complex as aset.c currently stores the freelist index in the MemoryChunk rather than the chunk_size. I did this because it saved having to call AllocSetFreeIndex() in AllocSetFree() which made a meaningful performance improvement in pfree(). The 0002 patch effectively reverses that change out so that the chunk_size is stored. Again, these patches are only intended to demonstrate the performance and check how it compares to bump. I'm yet uncertain why, but I find that the first time I run the query quoted above, the aset results are quite a bit slower than on subsequent runs. Other context types don't seem to suffer from this. The previous results I sent in [1] were of the initial run after starting up the database. The attached graph shows the number of seconds it takes to allocate a total of 1GBs of memory in various chunk sizes, resetting the context after 1MBs has been allocated, so as to keep the test sized so it fits in CPU caches. I'm not drawing any particular conclusion from the results aside from it's not quite as fast as bump. I also have some reservations about how easy it would be to actually use something like palloc_nofree(). For example heap_form_minimal_tuple() does palloc0(). What if I wanted to call ExecCopySlotMinimalTuple() and use palloc0_nofree(). Would we need new versions of various functions to give us control over this? David [1] https://www.postgresql.org/message-id/CAApHDvr_hGT=kap0yxbksnztbrx+6hukiecwen2bulww1ut...@mail.gmail.com
From 3980d35a11d7f27d18c57586a7f7539d86dafae3 Mon Sep 17 00:00:00 2001 From: David Rowley <dgrow...@gmail.com> Date: Tue, 26 Mar 2024 00:06:55 +1300 Subject: [PATCH 2/2] Modify aset.c to store the chunk size in the hdr --- src/backend/utils/mmgr/aset.c | 51 +++++++++++++---------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index c683e2a928..27006662c4 100644 --- a/src/backend/utils/mmgr/aset.c +++ b/src/backend/utils/mmgr/aset.c @@ -772,7 +772,7 @@ AllocSetAllocLarge(MemoryContext context, Size size, int flags) */ static inline void * AllocSetAllocChunkFromBlock(MemoryContext context, AllocBlock block, - Size size, Size chunk_size, int fidx) + Size size, Size chunk_size) { MemoryChunk *chunk; @@ -785,7 +785,7 @@ AllocSetAllocChunkFromBlock(MemoryContext context, AllocBlock block, Assert(block->freeptr <= block->endptr); /* store the free list index in the value field */ - MemoryChunkSetHdrMask(chunk, block, fidx, MCTX_ASET_ID); + MemoryChunkSetHdrMask(chunk, block, chunk_size, MCTX_ASET_ID); #ifdef MEMORY_CONTEXT_CHECKING chunk->requested_size = size; @@ -817,14 +817,13 @@ AllocSetAllocChunkFromBlock(MemoryContext context, AllocBlock block, pg_noinline static void * AllocSetAllocFromNewBlock(MemoryContext context, Size size, int flags, - int fidx) + Size chunk_size) { AllocSet set = (AllocSet) context; AllocBlock block; Size availspace; Size blksize; Size required_size; - Size chunk_size; /* due to the keeper block set->blocks should always be valid */ Assert(set->blocks != NULL); @@ -869,7 +868,7 @@ AllocSetAllocFromNewBlock(MemoryContext context, Size size, int flags, availspace -= (availchunk + ALLOC_CHUNKHDRSZ); /* store the freelist index in the value field */ - MemoryChunkSetHdrMask(chunk, block, a_fidx, MCTX_ASET_ID); + MemoryChunkSetHdrMask(chunk, block, availchunk, MCTX_ASET_ID); #ifdef MEMORY_CONTEXT_CHECKING chunk->requested_size = InvalidAllocSize; /* mark it free */ #endif @@ -892,8 +891,6 @@ AllocSetAllocFromNewBlock(MemoryContext context, Size size, int flags, if (set->nextBlockSize > set->maxBlockSize) set->nextBlockSize = set->maxBlockSize; - /* Choose the actual chunk size to allocate */ - chunk_size = GetChunkSizeFromFreeListIdx(fidx); Assert(chunk_size >= size); /* @@ -938,7 +935,7 @@ AllocSetAllocFromNewBlock(MemoryContext context, Size size, int flags, block->next->prev = block; set->blocks = block; - return AllocSetAllocChunkFromBlock(context, block, size, chunk_size, fidx); + return AllocSetAllocChunkFromBlock(context, block, size, chunk_size); } /* @@ -969,7 +966,9 @@ AllocSetAlloc(MemoryContext context, Size size, int flags) AllocSet set = (AllocSet) context; AllocBlock block; MemoryChunk *chunk; +#ifdef ALLOCSET_USE_FREELIST int fidx; +#endif Size chunk_size; Size availspace; @@ -985,6 +984,7 @@ AllocSetAlloc(MemoryContext context, Size size, int flags) if (size > set->allocChunkLimit) return AllocSetAllocLarge(context, size, flags); +#ifdef ALLOCSET_USE_FREELIST /* * Request is small enough to be treated as a chunk. Look in the * corresponding free list to see if there is a free chunk we could reuse. @@ -998,7 +998,6 @@ AllocSetAlloc(MemoryContext context, Size size, int flags) * doubling the memory requirements for such allocations. */ fidx = AllocSetFreeIndex(size); -#ifdef ALLOCSET_USE_FREELIST chunk = set->freelist[fidx]; if (chunk != NULL) { @@ -1007,7 +1006,7 @@ AllocSetAlloc(MemoryContext context, Size size, int flags) /* Allow access to the chunk header. */ VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); - Assert(fidx == MemoryChunkGetValue(chunk)); + Assert(fidx == AllocSetFreeIndex(MemoryChunkGetValue(chunk))); /* pop this chunk off the freelist */ VALGRIND_MAKE_MEM_DEFINED(link, sizeof(AllocFreeListLink)); @@ -1039,7 +1038,7 @@ AllocSetAlloc(MemoryContext context, Size size, int flags) /* * Choose the actual chunk size to allocate. */ - chunk_size = GetChunkSizeFromFreeListIdx(fidx); + chunk_size = GetChunkSizeFromFreeListIdx(AllocSetFreeIndex(size)); Assert(chunk_size >= size); block = set->blocks; @@ -1050,10 +1049,10 @@ AllocSetAlloc(MemoryContext context, Size size, int flags) * chunk into that block. Else must start a new one. */ if (unlikely(availspace < (chunk_size + ALLOC_CHUNKHDRSZ))) - return AllocSetAllocFromNewBlock(context, size, flags, fidx); + return AllocSetAllocFromNewBlock(context, size, flags, chunk_size); /* There's enough space on the current block, so allocate from that */ - return AllocSetAllocChunkFromBlock(context, block, size, chunk_size, fidx); + return AllocSetAllocChunkFromBlock(context, block, size, chunk_size); } /* @@ -1113,6 +1112,7 @@ AllocSetFree(void *pointer) AllocBlock block = MemoryChunkGetBlock(chunk); int fidx; AllocFreeListLink *link; + Size chunk_size; /* * In this path, for speed reasons we just Assert that the referenced @@ -1123,13 +1123,14 @@ AllocSetFree(void *pointer) Assert(AllocBlockIsValid(block)); set = block->aset; - fidx = MemoryChunkGetValue(chunk); + chunk_size = MemoryChunkGetValue(chunk); + fidx = AllocSetFreeIndex(chunk_size); Assert(FreeListIdxIsValid(fidx)); link = GetFreeListLink(chunk); #ifdef MEMORY_CONTEXT_CHECKING /* Test for someone scribbling on unused space in chunk */ - if (chunk->requested_size < GetChunkSizeFromFreeListIdx(fidx)) + if (chunk->requested_size < chunk_size) if (!sentinel_ok(pointer, chunk->requested_size)) elog(WARNING, "detected write past chunk end in %s %p", set->header.name, chunk); @@ -1174,7 +1175,6 @@ AllocSetRealloc(void *pointer, Size size, int flags) AllocSet set; MemoryChunk *chunk = PointerGetMemoryChunk(pointer); Size oldchksize; - int fidx; /* Allow access to the chunk header. */ VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); @@ -1313,9 +1313,7 @@ AllocSetRealloc(void *pointer, Size size, int flags) Assert(AllocBlockIsValid(block)); set = block->aset; - fidx = MemoryChunkGetValue(chunk); - Assert(FreeListIdxIsValid(fidx)); - oldchksize = GetChunkSizeFromFreeListIdx(fidx); + oldchksize = MemoryChunkGetValue(chunk); #ifdef MEMORY_CONTEXT_CHECKING /* Test for someone scribbling on unused space in chunk */ @@ -1464,7 +1462,6 @@ Size AllocSetGetChunkSpace(void *pointer) { MemoryChunk *chunk = PointerGetMemoryChunk(pointer); - int fidx; /* Allow access to the chunk header. */ VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); @@ -1481,13 +1478,10 @@ AllocSetGetChunkSpace(void *pointer) return block->endptr - (char *) chunk; } - fidx = MemoryChunkGetValue(chunk); - Assert(FreeListIdxIsValid(fidx)); - /* Disallow access to the chunk header. */ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); - return GetChunkSizeFromFreeListIdx(fidx) + ALLOC_CHUNKHDRSZ; + return MemoryChunkGetValue(chunk) + ALLOC_CHUNKHDRSZ; } /* @@ -1554,7 +1548,6 @@ AllocSetStats(MemoryContext context, /* Allow access to the chunk header. */ VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ); - Assert(MemoryChunkGetValue(chunk) == fidx); VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); freechunks++; @@ -1665,13 +1658,7 @@ AllocSetCheck(MemoryContext context) } else { - int fidx = MemoryChunkGetValue(chunk); - - if (!FreeListIdxIsValid(fidx)) - elog(WARNING, "problem in alloc set %s: bad chunk size for chunk %p in block %p", - name, chunk, block); - - chsize = GetChunkSizeFromFreeListIdx(fidx); /* aligned chunk size */ + chsize = MemoryChunkGetValue(chunk); /* aligned chunk size */ /* * Check the stored block offset correctly references this -- 2.40.1.windows.1
From 774edcd23cc68b526bbb971db55f6eb30bf8a763 Mon Sep 17 00:00:00 2001 From: David Rowley <dgrow...@gmail.com> Date: Mon, 25 Mar 2024 22:04:51 +1300 Subject: [PATCH 1/2] Modify AllocSetAlloc not to look at freelists --- src/backend/utils/mmgr/aset.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 751cc3408c..c683e2a928 100644 --- a/src/backend/utils/mmgr/aset.c +++ b/src/backend/utils/mmgr/aset.c @@ -998,6 +998,7 @@ AllocSetAlloc(MemoryContext context, Size size, int flags) * doubling the memory requirements for such allocations. */ fidx = AllocSetFreeIndex(size); +#ifdef ALLOCSET_USE_FREELIST chunk = set->freelist[fidx]; if (chunk != NULL) { @@ -1033,6 +1034,7 @@ AllocSetAlloc(MemoryContext context, Size size, int flags) return MemoryChunkGetPointer(chunk); } +#endif /* * Choose the actual chunk size to allocate. -- 2.40.1.windows.1