allocate_performance_functions.patch.txt

text/plain

Filename: allocate_performance_functions.patch.txt
Type: text/plain
Part: 8
Message: Re: Reducing the chunk header sizes on all memory context types
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index bb7cc94024..6a0c1b53dc 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -193,3 +193,199 @@ 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();
+}
+
+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);
+	char   *context_type = PG_GETARG_CSTRING(3);
+	int64	curr_memory_use = 0;
+	int64	remaining_alloc_bytes = total_alloc;
+	MemoryContext context;
+	MemoryContext oldContext;
+	void	   *curr_alloc;
+
+	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);
+
+	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)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		/* Allocate the memory and update the counters */
+		curr_alloc = 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
+
+		/*
+		 * If the currently allocated memory has reached or exceeded the amount
+		 * of memory we want to keep allocated at once then reset the context.
+		 */
+		if (curr_memory_use >= keep_memory)
+		{
+			curr_memory_use = 0;
+			MemoryContextReset(context);
+		}
+	}
+
+	MemoryContextSwitchTo(oldContext);
+	MemoryContextDelete(context);
+
+	PG_RETURN_VOID();
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index be47583122..c3a7383a6d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8101,6 +8101,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 => 'void', proargtypes => 'int4 int8 int8 cstring',
+  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 => 'void', proargtypes => 'int4 int8 int8 cstring',
+  prosrc => 'pg_allocate_memory_test_reset' },
+
 # non-persistent series generator
 { oid => '1066', descr => 'non-persistent series generator',
   proname => 'generate_series', prorows => '1000',