On Thu, 4 Apr 2024 at 22:43, Tom Lane <t...@sss.pgh.pa.us> wrote: > > Matthias van de Meent <boekewurm+postg...@gmail.com> writes: > > On Mon, 25 Mar 2024 at 22:44, Tom Lane <t...@sss.pgh.pa.us> wrote: > >> Basically, I'm not happy with consuming the last reasonably-available > >> pattern for a memory context type that has little claim to being the > >> Last Context Type We Will Ever Want. Rather than making a further > >> dent in our ability to detect corrupted chunks, we should do something > >> towards restoring the expansibility that existed in the original > >> design. Then we can add bump contexts and whatever else we want. > > > So, would something like the attached make enough IDs available so > > that we can add the bump context anyway? > > > It extends memory context IDs to 5 bits (32 values), of which > > - 8 have glibc's malloc pattern of 001/010; > > - 1 is unused memory's 00000 > > - 1 is wipe_mem's 11111 > > - 4 are used by existing contexts (Aset/Generation/Slab/AlignedRedirect) > > - 18 are newly available. > > This seems like it would solve the problem for a good long time > to come; and if we ever need more IDs, we could steal one more bit > by requiring the offset to the block header to be a multiple of 8. > (Really, we could just about do that today at little or no cost ... > machines with MAXALIGN less than 8 are very thin on the ground.)
Hmm, it seems like a decent idea, but I didn't want to deal with the repercussions of that this late in the cycle when these 2 bits were still relatively easy to get hold of. > The only objection I can think of is that perhaps this would slow > things down a tad by requiring more complicated shifting/masking. > I wonder if we could redo the performance checks that were done > on the way to accepting the current design. I didn't do very extensive testing, but the light performance tests that I did with the palloc performance benchmark patch & script shared above indicate didn't measure an observable negative effect. An adapted version of the test that uses repalloc() to check performance differences in MCXT_METHOD() doesn't show a significant performance difference from master either. That test case is attached as repalloc-performance-test-function.patch.txt. The full set of patches would then accumulate to the attached v5 of the patchset. 0001 is an update of my patch from yesterday, in which I update MemoryContextMethodID infrastructure for more IDs, and use a new naming scheme for unused/reserved IDs. 0002 and 0003 are David's patches, with minor changes to work with 0001 (rebasing, and I moved the location around to keep function declaration in order with memctx ids) Kind regards, Matthias van de Meent
From b65320736cf63684b4710b3d63e243a0862d37c6 Mon Sep 17 00:00:00 2001 From: Matthias van de Meent <boekewurm+postg...@gmail.com> Date: Fri, 5 Apr 2024 15:13:52 +0200 Subject: [PATCH] repalloc performance test function --- src/backend/utils/adt/mcxtfuncs.c | 231 ++++++++++++++++++++++++++++++ src/include/catalog/pg_proc.dat | 12 ++ 2 files changed, 243 insertions(+) diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c index 4d4a70915b..88de4bbcb5 100644 --- a/src/backend/utils/adt/mcxtfuncs.c +++ b/src/backend/utils/adt/mcxtfuncs.c @@ -15,8 +15,11 @@ #include "postgres.h" +#include <time.h> + #include "funcapi.h" #include "mb/pg_wchar.h" +#include "miscadmin.h" #include "storage/proc.h" #include "storage/procarray.h" #include "utils/builtins.h" @@ -185,3 +188,231 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS) PG_RETURN_BOOL(true); } + +typedef struct AllocateTestNext +{ + struct AllocateTestNext *next; /* ptr to the next allocation */ +} AllocateTestNext; + +/* #define ALLOCATE_TEST_DEBUG */ +/* + * pg_allocate_memory_test + * Used to test the performance of a memory context types + */ +Datum +pg_allocate_memory_test(PG_FUNCTION_ARGS) +{ + int32 chunk_size = PG_GETARG_INT32(0); + int64 keep_memory = PG_GETARG_INT64(1); + int64 total_alloc = PG_GETARG_INT64(2); + text *context_type_text = PG_GETARG_TEXT_PP(3); + char *context_type; + int64 curr_memory_use = 0; + int64 remaining_alloc_bytes = total_alloc; + MemoryContext context; + MemoryContext oldContext; + AllocateTestNext *next_free_ptr = NULL; + AllocateTestNext *last_alloc = NULL; + clock_t start, end; + + if (chunk_size < sizeof(AllocateTestNext)) + elog(ERROR, "chunk_size (%d) must be at least %ld bytes", chunk_size, + sizeof(AllocateTestNext)); + if (keep_memory > total_alloc) + elog(ERROR, "keep_memory (" INT64_FORMAT ") must be less than total_alloc (" INT64_FORMAT ")", + keep_memory, total_alloc); + + context_type = text_to_cstring(context_type_text); + + start = clock(); + + if (strcmp(context_type, "generation") == 0) + context = GenerationContextCreate(CurrentMemoryContext, + "pg_allocate_memory_test", + ALLOCSET_DEFAULT_SIZES); + else if (strcmp(context_type, "aset") == 0) + context = AllocSetContextCreate(CurrentMemoryContext, + "pg_allocate_memory_test", + ALLOCSET_DEFAULT_SIZES); + else if (strcmp(context_type, "slab") == 0) + context = SlabContextCreate(CurrentMemoryContext, + "pg_allocate_memory_test", + ALLOCSET_DEFAULT_MAXSIZE, + chunk_size); + else + elog(ERROR, "context_type must be \"generation\", \"aset\" or \"slab\""); + + oldContext = MemoryContextSwitchTo(context); + + while (remaining_alloc_bytes > 0) + { + AllocateTestNext *curr_alloc; + + CHECK_FOR_INTERRUPTS(); + + /* Allocate the memory and update the counters */ + curr_alloc = (AllocateTestNext *) palloc(chunk_size); + remaining_alloc_bytes -= chunk_size; + curr_memory_use += chunk_size; + +#ifdef ALLOCATE_TEST_DEBUG + elog(NOTICE, "alloc %p (curr_memory_use " INT64_FORMAT " bytes, remaining_alloc_bytes " INT64_FORMAT ")", curr_alloc, curr_memory_use, remaining_alloc_bytes); +#endif + + /* + * Point the last allocate to this one so that we can free allocations + * starting with the oldest first. + */ + curr_alloc->next = NULL; + if (last_alloc != NULL) + last_alloc->next = curr_alloc; + + if (next_free_ptr == NULL) + { + /* + * Remember the first chunk to free. We will follow the ->next + * pointers to find the next chunk to free when freeing memory + */ + next_free_ptr = curr_alloc; + } + + /* + * If the currently allocated memory has reached or exceeded the amount + * of memory we want to keep allocated at once then we'd better free + * some. Since all allocations are the same size we only need to free + * one allocation per loop. + */ + if (curr_memory_use >= keep_memory) + { + AllocateTestNext *next = next_free_ptr->next; + + /* free the memory and update the current memory usage */ + pfree(next_free_ptr); + curr_memory_use -= chunk_size; + +#ifdef ALLOCATE_TEST_DEBUG + elog(NOTICE, "free %p (curr_memory_use " INT64_FORMAT " bytes, remaining_alloc_bytes " INT64_FORMAT ")", next_free_ptr, curr_memory_use, remaining_alloc_bytes); +#endif + /* get the next chunk to free */ + next_free_ptr = next; + } + + if (curr_memory_use > 0) + last_alloc = curr_alloc; + else + last_alloc = NULL; + } + + /* cleanup loop -- pfree remaining memory */ + while (next_free_ptr != NULL) + { + AllocateTestNext *next = next_free_ptr->next; + + /* free the memory and update the current memory usage */ + pfree(next_free_ptr); + curr_memory_use -= chunk_size; + +#ifdef ALLOCATE_TEST_DEBUG + elog(NOTICE, "free %p (curr_memory_use " INT64_FORMAT " bytes, remaining_alloc_bytes " INT64_FORMAT ")", next_free_ptr, curr_memory_use, remaining_alloc_bytes); +#endif + + next_free_ptr = next; + } + + MemoryContextSwitchTo(oldContext); + + end = clock(); + + PG_RETURN_FLOAT8((double) (end - start) / CLOCKS_PER_SEC); +} + +Datum +pg_allocate_memory_test_reset(PG_FUNCTION_ARGS) +{ + int32 chunk_size = PG_GETARG_INT32(0); + int64 keep_memory = PG_GETARG_INT64(1); + int64 total_alloc = PG_GETARG_INT64(2); + int64 n_ptrs; + text *context_type_text = PG_GETARG_TEXT_PP(3); + char *context_type; + int64 curr_memory_use = 0; + int64 remaining_alloc_bytes = total_alloc; + MemoryContext context; + MemoryContext oldContext; + clock_t start, end; + int64 **ptrs; + uint32 lcg_value = 0; + uint32 lcg_c = 1013904223; + uint32 lcg_a = 1664525; + + if (chunk_size < 1) + elog(ERROR, "size of chunk must be above 0"); + if (keep_memory > total_alloc) + elog(ERROR, "keep_memory (" INT64_FORMAT ") must be less than total_alloc (" INT64_FORMAT ")", + keep_memory, total_alloc); + + context_type = text_to_cstring(context_type_text); + n_ptrs = 1 << pg_leftmost_one_pos64(keep_memory / chunk_size); + + ptrs = (int64 **) palloc0(n_ptrs * sizeof(int64 *)); + + start = clock(); + + if (strcmp(context_type, "generation") == 0) + context = GenerationContextCreate(CurrentMemoryContext, + "pg_allocate_memory_test", + ALLOCSET_DEFAULT_SIZES); + else if (strcmp(context_type, "aset") == 0) + context = AllocSetContextCreate(CurrentMemoryContext, + "pg_allocate_memory_test", + ALLOCSET_DEFAULT_SIZES); + else if (strcmp(context_type, "slab") == 0) + context = SlabContextCreate(CurrentMemoryContext, + "pg_allocate_memory_test", + ALLOCSET_DEFAULT_MAXSIZE, + chunk_size); +// else if (strcmp(context_type, "bump") == 0) +// context = BumpContextCreate(CurrentMemoryContext, +// "pg_allocate_memory_test", +// ALLOCSET_DEFAULT_SIZES); + else + elog(ERROR, "context_type must be \"generation\", \"aset\" or \"slab\""); + + oldContext = MemoryContextSwitchTo(context); + + for (int i = 0; i < n_ptrs && remaining_alloc_bytes > 0;) + { + CHECK_FOR_INTERRUPTS(); + + /* Allocate the memory and update the counters */ + ptrs[i++] = (int64 *) palloc(chunk_size); + remaining_alloc_bytes -= chunk_size; + curr_memory_use += chunk_size; + } + + while (remaining_alloc_bytes > 0) + { + uint32 index; + CHECK_FOR_INTERRUPTS(); + index = lcg_value % (uint32) n_ptrs; + lcg_value = lcg_value * lcg_a + lcg_c; + Assert(PointerIsValid(ptr)); + + /* reallocate, and update the counters */ + ptrs[index] = (int64 *) repalloc(ptrs[index], chunk_size); + + remaining_alloc_bytes -= chunk_size; + curr_memory_use += chunk_size; + +#ifdef ALLOCATE_TEST_DEBUG + elog(NOTICE, "alloc %p (curr_memory_use " INT64_FORMAT " bytes, remaining_alloc_bytes " INT64_FORMAT ")", curr_alloc, curr_memory_use, remaining_alloc_bytes); +#endif + } + + MemoryContextSwitchTo(oldContext); + MemoryContextDelete(context); + + end = clock(); + + PG_RETURN_FLOAT8((double) (end - start) / CLOCKS_PER_SEC); +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 153d816a05..15d191db8b 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8288,6 +8288,18 @@ prorettype => 'bool', proargtypes => 'int4', prosrc => 'pg_log_backend_memory_contexts' }, +# just for testing memory context allocation speed +{ oid => '9319', descr => 'for testing performance of allocation and freeing', + proname => 'pg_allocate_memory_test', provolatile => 'v', + prorettype => 'float8', proargtypes => 'int4 int8 int8 text', + prosrc => 'pg_allocate_memory_test' }, + +# just for testing memory context allocation speed +{ oid => '9320', descr => 'for testing performance of allocation and resetting', + proname => 'pg_allocate_memory_test_reset', provolatile => 'v', + prorettype => 'float8', proargtypes => 'int4 int8 int8 text', + prosrc => 'pg_allocate_memory_test_reset' }, + # non-persistent series generator { oid => '1066', descr => 'non-persistent series generator', proname => 'generate_series', prorows => '1000', -- 2.40.1
v5-0002-Introduce-a-bump-memory-allocator.patch
Description: Binary data
v5-0001-Add-bitspace-for-more-memory-context-types-in-Mem.patch
Description: Binary data
v5-0003-Use-bump-memory-context-for-tuplesorts.patch
Description: Binary data