diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c index bb7cc94024..bdc5a387fc 100644 --- a/src/backend/utils/adt/mcxtfuncs.c +++ b/src/backend/utils/adt/mcxtfuncs.c @@ -193,3 +193,132 @@ 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); + char *context_type = PG_GETARG_CSTRING(3); + int64 curr_memory_use = 0; + int64 remaining_alloc_bytes = total_alloc; + MemoryContext context; + MemoryContext oldContext; + AllocateTestNext *next_free_ptr = NULL; + AllocateTestNext *last_alloc = NULL; + + 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); + + 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); + + PG_RETURN_VOID(); +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index be47583122..38dd030c8e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8101,6 +8101,12 @@ 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 => 'void', proargtypes => 'int4 int8 int8 cstring', + prosrc => 'pg_allocate_memory_test' }, + # non-persistent series generator { oid => '1066', descr => 'non-persistent series generator', proname => 'generate_series', prorows => '1000',