v3-0002-fixup-Optimize-palloc-etc-to-allow-sibling-calls.patch

text/plain

Filename: v3-0002-fixup-Optimize-palloc-etc-to-allow-sibling-calls.patch
Type: text/plain
Part: 1
Message: Re: Avoid stack frame setup in performance critical routines using tail calls

Patch

Format: format-patch
Series: patch v3-0002
Subject: fixup! Optimize palloc() etc to allow sibling calls
File+
src/backend/utils/mmgr/mcxt.c 64 3
src/backend/utils/mmgr/slab.c 4 7
src/include/nodes/memnodes.h 38 0
src/include/utils/memutils_internal.h 4 3
From 9b6ffdceb3c6f592d3d1597b19f1240d4a78d848 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Fri, 23 Feb 2024 00:27:07 +1300
Subject: [PATCH v3 2/2] fixup! Optimize palloc() etc to allow sibling calls

---
 src/backend/utils/mmgr/mcxt.c         | 67 +++++++++++++++++++++++++--
 src/backend/utils/mmgr/slab.c         | 11 ++---
 src/include/nodes/memnodes.h          | 38 +++++++++++++++
 src/include/utils/memutils_internal.h |  7 +--
 4 files changed, 110 insertions(+), 13 deletions(-)

diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 2947b2046b..c214d323c8 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1023,6 +1023,12 @@ MemoryContextCreate(MemoryContext node,
 	VALGRIND_CREATE_MEMPOOL(node, 0, false);
 }
 
+/*
+ * MemoryContextAllocationFailure
+ *		For use by MemoryContextMethods implementations to handle when malloc
+ *		returns NULL.  The bahavior is specific to whether MCXT_ALLOC_NO_OOM
+ *		is in 'flags'.
+ */
 void *
 MemoryContextAllocationFailure(MemoryContext context, Size size, int flags)
 {
@@ -1038,6 +1044,11 @@ MemoryContextAllocationFailure(MemoryContext context, Size size, int flags)
 	return NULL;
 }
 
+/*
+ * MemoryContextSizeFailure
+ *		For use by MemoryContextMethods implementations to handle invalid
+ *		memory allocation request sizes.
+ */
 void
 MemoryContextSizeFailure(MemoryContext context, Size size, int flags)
 {
@@ -1061,6 +1072,16 @@ MemoryContextAlloc(MemoryContext context, Size size)
 
 	context->isReset = false;
 
+	/*
+	 * For efficiency reasons, we purposefully offload the handling of
+	 * allocation failures to the MemoryContextMethods implementation as this
+	 * allows these checks to be performed only when an actual malloc needs to
+	 * be done to request more memory from the OS.  Additionally, not having
+	 * to execute any instructions after this call allows the compiler to use
+	 * the tailcall optimization.  If you're considering adding code after
+	 * this call, consider making it the responsibility of the 'alloc'
+	 * function instead.
+	 */
 	ret = context->methods->alloc(context, size, 0);
 
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
@@ -1192,9 +1213,19 @@ palloc(Size size)
 
 	context->isReset = false;
 
+	/*
+	 * For efficiency reasons, we purposefully offload the handling of
+	 * allocation failures to the MemoryContextMethods implementation as this
+	 * allows these checks to be performed only when an actual malloc needs to
+	 * be done to request more memory from the OS.  Additionally, not having
+	 * to execute any instructions after this call allows the compiler to use
+	 * the tailcall optimization.  If you're considering adding code after
+	 * this call, consider making it the responsibility of the 'alloc'
+	 * function instead.
+	 */
 	ret = context->methods->alloc(context, size, 0);
-
-	Assert(ret != 0);
+	/* We expect OOM to be handled by the alloc function */
+	Assert(ret != NULL);
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
 	return ret;
@@ -1410,10 +1441,20 @@ repalloc(void *pointer, Size size)
 	/* isReset must be false already */
 	Assert(!context->isReset);
 
+	/*
+	 * For efficiency reasons, we purposefully offload the handling of
+	 * allocation failures to the MemoryContextMethods implementation as this
+	 * allows these checks to be performed only when an actual malloc needs to
+	 * be done to request more memory from the OS.  Additionally, not having
+	 * to execute any instructions after this call allows the compiler to use
+	 * the tailcall optimization.  If you're considering adding code after
+	 * this call, consider making it the responsibility of the 'realloc'
+	 * function instead.
+	 */
 	ret = MCXT_METHOD(pointer, realloc) (pointer, size, 0);
 
 #ifdef USE_VALGRIND
-	if (ret != NULL && method != MCTX_ALIGNED_REDIRECT_ID)
+	if (method != MCTX_ALIGNED_REDIRECT_ID)
 		VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
 #endif
 
@@ -1438,6 +1479,16 @@ repalloc_extended(void *pointer, Size size, int flags)
 	/* isReset must be false already */
 	Assert(!context->isReset);
 
+	/*
+	 * For efficiency reasons, we purposefully offload the handling of
+	 * allocation failures to the MemoryContextMethods implementation as this
+	 * allows these checks to be performed only when an actual malloc needs to
+	 * be done to request more memory from the OS.  Additionally, not having
+	 * to execute any instructions after this call allows the compiler to use
+	 * the tailcall optimization.  If you're considering adding code after
+	 * this call, consider making it the responsibility of the 'realloc'
+	 * function instead.
+	 */
 	ret = MCXT_METHOD(pointer, realloc) (pointer, size, flags);
 	if (unlikely(ret == NULL))
 		return NULL;
@@ -1483,6 +1534,16 @@ MemoryContextAllocHuge(MemoryContext context, Size size)
 
 	context->isReset = false;
 
+	/*
+	 * For efficiency reasons, we purposefully offload the handling of
+	 * allocation failures to the MemoryContextMethods implementation as this
+	 * allows these checks to be performed only when an actual malloc needs to
+	 * be done to request more memory from the OS.  Additionally, not having
+	 * to execute any instructions after this call allows the compiler to use
+	 * the tailcall optimization.  If you're considering adding code after
+	 * this call, consider making it the responsibility of the 'alloc'
+	 * function instead.
+	 */
 	ret = context->methods->alloc(context, size, MCXT_ALLOC_HUGE);
 
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c
index 1345c8d500..bc91446cb3 100644
--- a/src/backend/utils/mmgr/slab.c
+++ b/src/backend/utils/mmgr/slab.c
@@ -504,17 +504,14 @@ SlabAlloc(MemoryContext context, Size size, int flags)
 
 	Assert(SlabIsValid(slab));
 
-	/*
-	 * XXX: Probably no need to check for huge allocations, we only support
-	 * one size? Which could theoretically be huge, but that'd not make
-	 * sense...
-	 */
-
 	/* sanity check that this is pointing to a valid blocklist */
 	Assert(slab->curBlocklistIndex >= 0);
 	Assert(slab->curBlocklistIndex <= SlabBlocklistIndex(slab, slab->chunksPerBlock));
 
-	/* make sure we only allow correct request size */
+	/*
+	 * Make sure we only allow correct request size.  This doubles as the
+	 * MemoryContextCheckSize check.
+	 */
 	if (unlikely(size != slab->chunkSize))
 		elog(ERROR, "unexpected alloc chunk size %zu (expected %u)",
 			 size, slab->chunkSize);
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index cc8cc58a0c..edc0257f36 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -57,20 +57,58 @@ typedef void (*MemoryStatsPrintFunc) (MemoryContext context, void *passthru,
 
 typedef struct MemoryContextMethods
 {
+	/*
+	 * Function to handle memory allocation requests of 'size' to allocate
+	 * memory into the given 'context'.  The function must handle flags
+	 * MCXT_ALLOC_HUGE and MCXT_ALLOC_NO_OOM.  MCXT_ALLOC_ZERO is handled by
+	 * the calling function.
+	 */
 	void	   *(*alloc) (MemoryContext context, Size size, int flags);
+
 	/* call this free_p in case someone #define's free() */
 	void		(*free_p) (void *pointer);
+
+	/*
+	 * Function to handle a size change request for an existing allocation.
+	 * The implementation must handle flags MCXT_ALLOC_HUGE and
+	 * MCXT_ALLOC_NO_OOM.  MCXT_ALLOC_ZERO is handled by the calling function.
+	 */
 	void	   *(*realloc) (void *pointer, Size size, int flags);
+
+	/*
+	 * Invalidate all previous allocations in the given memory context and
+	 * prepare the context for a new set of allocations.  Implementations may
+	 * optionally free() excess memory back to the OS during this time.
+	 */
 	void		(*reset) (MemoryContext context);
+
+	/* Free all memory consumed by the given MemoryContext. */
 	void		(*delete_context) (MemoryContext context);
+
+	/* Return the MemoryContext that the given pointer belongs to. */
 	MemoryContext (*get_chunk_context) (void *pointer);
+
+	/*
+	 * Return the number of bytes consumed by the given pointer within its
+	 * memory context, including the overhead of alignment and chunk headers.
+	 */
 	Size		(*get_chunk_space) (void *pointer);
+
+	/*
+	 * Return true if the given MemoryContext has not had any allocations
+	 * since it was created or last reset.
+	 */
 	bool		(*is_empty) (MemoryContext context);
 	void		(*stats) (MemoryContext context,
 						  MemoryStatsPrintFunc printfunc, void *passthru,
 						  MemoryContextCounters *totals,
 						  bool print_to_stderr);
 #ifdef MEMORY_CONTEXT_CHECKING
+
+	/*
+	 * Perform validation checks on the given context and raise any discovered
+	 * anomalies as WARNINGs.
+	 */
 	void		(*check) (MemoryContext context);
 #endif
 } MemoryContextMethods;
diff --git a/src/include/utils/memutils_internal.h b/src/include/utils/memutils_internal.h
index e5cecd7122..ad1048fd82 100644
--- a/src/include/utils/memutils_internal.h
+++ b/src/include/utils/memutils_internal.h
@@ -133,9 +133,11 @@ extern void MemoryContextCreate(MemoryContext node,
 								MemoryContext parent,
 								const char *name);
 
-extern void *MemoryContextAllocationFailure(MemoryContext context, Size size, int flags);
+extern void *MemoryContextAllocationFailure(MemoryContext context, Size size,
+											int flags);
 
-extern void MemoryContextSizeFailure(MemoryContext context, Size size, int flags) pg_attribute_noreturn();
+extern void MemoryContextSizeFailure(MemoryContext context, Size size,
+									 int flags) pg_attribute_noreturn();
 
 static inline void
 MemoryContextCheckSize(MemoryContext context, Size size, int flags)
@@ -147,5 +149,4 @@ MemoryContextCheckSize(MemoryContext context, Size size, int flags)
 	}
 }
 
-
 #endif							/* MEMUTILS_INTERNAL_H */
-- 
2.40.1