On Sat, 2025-03-22 at 09:39 -0700, Jeff Davis wrote:
> For some reason I'm getting a decline of about 3% in the c.sql test
> that seems to be associated with the accessor functions, even when
> inlined. I'm also not seeing as much benefit from the inlining of the
> MemoryContextMemAllocated(). But these mixed test results are minor
> compared with the memory savings of 35% and the more consistently-
> improved performance of 5% on the larger test (which is also
> integers),
> so I plan to commit it.
Committed, except for v9-0007. (Note that some commits were combined;
they were only separated originally for performance testing.)
I attached a new version of v9-0007, now v10-0001, that uses
recurse=false for two of the memory contexts. I didn't see a major
speedup, but posting here anyway.
Regards,
Jeff Davis
From 33db55a517a413109daf249369a20ae4b8bd2ab9 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Tue, 4 Mar 2025 13:21:35 -0800
Subject: [PATCH v10] Inline MemoryContextMemAllocated().
Suggested-by: David Rowley <[email protected]>
Discussion: https://postgr.es/m/CAApHDvpN4v3t_sdz4dvrv1Fx_ZPw=twsnxuteytryp7lfz5...@mail.gmail.com
---
src/backend/executor/nodeAgg.c | 8 ++--
src/backend/utils/mmgr/mcxt.c | 68 ----------------------------------
src/include/nodes/memnodes.h | 68 ++++++++++++++++++++++++++++++++++
src/include/utils/memutils.h | 1 -
4 files changed, 72 insertions(+), 73 deletions(-)
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index f83fc16c5c8..f2c0e535b8f 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1868,9 +1868,9 @@ hash_agg_check_limits(AggState *aggstate)
{
uint64 ngroups = aggstate->hash_ngroups_current;
Size meta_mem = MemoryContextMemAllocated(aggstate->hash_metacxt,
- true);
+ false);
Size entry_mem = MemoryContextMemAllocated(aggstate->hash_tablecxt,
- true);
+ false);
Size tval_mem = MemoryContextMemAllocated(aggstate->hashcontext->ecxt_per_tuple_memory,
true);
Size total_mem = meta_mem + entry_mem + tval_mem;
@@ -1957,10 +1957,10 @@ hash_agg_update_metrics(AggState *aggstate, bool from_tape, int npartitions)
return;
/* memory for the hash table itself */
- meta_mem = MemoryContextMemAllocated(aggstate->hash_metacxt, true);
+ meta_mem = MemoryContextMemAllocated(aggstate->hash_metacxt, false);
/* memory for hash entries */
- entry_mem = MemoryContextMemAllocated(aggstate->hash_tablecxt, true);
+ entry_mem = MemoryContextMemAllocated(aggstate->hash_tablecxt, false);
/* memory for byref transition states */
hashkey_mem = MemoryContextMemAllocated(aggstate->hashcontext->ecxt_per_tuple_memory, true);
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 91060de0ab7..ea08bcf8ed2 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -232,50 +232,6 @@ GetMemoryChunkHeader(const void *pointer)
return header;
}
-/*
- * MemoryContextTraverseNext
- * Helper function to traverse all descendants of a memory context
- * without recursion.
- *
- * Recursion could lead to out-of-stack errors with deep context hierarchies,
- * which would be unpleasant in error cleanup code paths.
- *
- * To process 'context' and all its descendants, use a loop like this:
- *
- * <process 'context'>
- * for (MemoryContext curr = context->firstchild;
- * curr != NULL;
- * curr = MemoryContextTraverseNext(curr, context))
- * {
- * <process 'curr'>
- * }
- *
- * This visits all the contexts in pre-order, that is a node is visited
- * before its children.
- */
-static MemoryContext
-MemoryContextTraverseNext(MemoryContext curr, MemoryContext top)
-{
- /* After processing a node, traverse to its first child if any */
- if (curr->firstchild != NULL)
- return curr->firstchild;
-
- /*
- * After processing a childless node, traverse to its next sibling if
- * there is one. If there isn't, traverse back up to the parent (which
- * has already been visited, and now so have all its descendants). We're
- * done if that is "top", otherwise traverse to its next sibling if any,
- * otherwise repeat moving up.
- */
- while (curr->nextchild == NULL)
- {
- curr = curr->parent;
- if (curr == top)
- return NULL;
- }
- return curr->nextchild;
-}
-
/*
* Support routines to trap use of invalid memory context method IDs
* (from calling pfree or the like on a bogus pointer). As a possible
@@ -754,30 +710,6 @@ MemoryContextIsEmpty(MemoryContext context)
return context->methods->is_empty(context);
}
-/*
- * Find the memory allocated to blocks for this memory context. If recurse is
- * true, also include children.
- */
-Size
-MemoryContextMemAllocated(MemoryContext context, bool recurse)
-{
- Size total = context->mem_allocated;
-
- Assert(MemoryContextIsValid(context));
-
- if (recurse)
- {
- for (MemoryContext curr = context->firstchild;
- curr != NULL;
- curr = MemoryContextTraverseNext(curr, context))
- {
- total += curr->mem_allocated;
- }
- }
-
- return total;
-}
-
/*
* Return the memory consumption statistics about the given context and its
* children.
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index 5807ef805bd..969b4d6ef33 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -149,4 +149,72 @@ typedef struct MemoryContextData
IsA((context), GenerationContext) || \
IsA((context), BumpContext)))
+/*
+ * MemoryContextTraverseNext
+ * Helper function to traverse all descendants of a memory context
+ * without recursion.
+ *
+ * Recursion could lead to out-of-stack errors with deep context hierarchies,
+ * which would be unpleasant in error cleanup code paths.
+ *
+ * To process 'context' and all its descendants, use a loop like this:
+ *
+ * <process 'context'>
+ * for (MemoryContext curr = context->firstchild;
+ * curr != NULL;
+ * curr = MemoryContextTraverseNext(curr, context))
+ * {
+ * <process 'curr'>
+ * }
+ *
+ * This visits all the contexts in pre-order, that is a node is visited
+ * before its children.
+ */
+static inline MemoryContext
+MemoryContextTraverseNext(MemoryContext curr, MemoryContext top)
+{
+ /* After processing a node, traverse to its first child if any */
+ if (curr->firstchild != NULL)
+ return curr->firstchild;
+
+ /*
+ * After processing a childless node, traverse to its next sibling if
+ * there is one. If there isn't, traverse back up to the parent (which
+ * has already been visited, and now so have all its descendants). We're
+ * done if that is "top", otherwise traverse to its next sibling if any,
+ * otherwise repeat moving up.
+ */
+ while (curr->nextchild == NULL)
+ {
+ curr = curr->parent;
+ if (curr == top)
+ return NULL;
+ }
+ return curr->nextchild;
+}
+
+/*
+ * Find the memory allocated to blocks for this memory context. If recurse is
+ * true, also include children.
+ */
+static inline Size
+MemoryContextMemAllocated(MemoryContext context, bool recurse)
+{
+ Size total = context->mem_allocated;
+
+ Assert(MemoryContextIsValid(context));
+
+ if (recurse)
+ {
+ for (MemoryContext curr = context->firstchild;
+ curr != NULL;
+ curr = MemoryContextTraverseNext(curr, context))
+ {
+ total += curr->mem_allocated;
+ }
+ }
+
+ return total;
+}
+
#endif /* MEMNODES_H */
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 8abc26abce2..4f153d0e067 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -83,7 +83,6 @@ extern MemoryContext GetMemoryChunkContext(void *pointer);
extern Size GetMemoryChunkSpace(void *pointer);
extern MemoryContext MemoryContextGetParent(MemoryContext context);
extern bool MemoryContextIsEmpty(MemoryContext context);
-extern Size MemoryContextMemAllocated(MemoryContext context, bool recurse);
extern void MemoryContextMemConsumed(MemoryContext context,
MemoryContextCounters *consumed);
extern void MemoryContextStats(MemoryContext context);
--
2.34.1