Hi,
When I am working on "shared detoast value"[0], where I want to avoid
detoast the same datum over and over again, I have to decide which
memory context should be used to hold the detoast value. later I
found I have to use different MemoryContexts for the OuterTuple and
innerTuple since OuterTuple usually have a longer lifespan.
I found the following code in nodeMergeJoin.c which has pretty similar
situation, just that it uses ExprContext rather than MemoryContext.
MergeJoinState *
ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
/*
* we need two additional econtexts in which we can compute the join
* expressions from the left and right input tuples. The node's regular
* econtext won't do because it gets reset too often.
*/
mergestate->mj_OuterEContext = CreateExprContext(estate);
mergestate->mj_InnerEContext = CreateExprContext(estate);
IIUC, we needs a MemoryContext rather than ExprContext in fact. In the
attachment, I just use two MemoryContext instead of the two ExprContexts
which should be less memory and more precise semantics, and works
fine. shall we go in this direction? I attached the 2 MemoryContext in
JoinState rather than MergeJoinState, which is for the "shared detoast
value"[0] more or less.
[0] https://www.postgresql.org/message-id/[email protected]
>From cadd94f8ff2398ca430b43494b3eb71225b017c3 Mon Sep 17 00:00:00 2001
From: "yizhi.fzh" <[email protected]>
Date: Fri, 15 Dec 2023 15:28:36 +0800
Subject: [PATCH v1] Use MemoryContext instead of ExprContext for
nodeMergejoin.c
---
src/backend/executor/nodeMergejoin.c | 27 ++++++++++++++-------------
src/backend/executor/nodeNestloop.c | 1 +
src/include/nodes/execnodes.h | 4 ++--
3 files changed, 17 insertions(+), 15 deletions(-)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 3cdab77dfc..d330e104f6 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -294,7 +294,7 @@ MJExamineQuals(List *mergeclauses,
static MJEvalResult
MJEvalOuterValues(MergeJoinState *mergestate)
{
- ExprContext *econtext = mergestate->mj_OuterEContext;
+ ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
MJEvalResult result = MJEVAL_MATCHABLE;
int i;
MemoryContext oldContext;
@@ -303,9 +303,9 @@ MJEvalOuterValues(MergeJoinState *mergestate)
if (TupIsNull(mergestate->mj_OuterTupleSlot))
return MJEVAL_ENDOFJOIN;
- ResetExprContext(econtext);
+ MemoryContextReset(mergestate->js.outer_tuple_memory);
- oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ oldContext = MemoryContextSwitchTo(mergestate->js.outer_tuple_memory);
econtext->ecxt_outertuple = mergestate->mj_OuterTupleSlot;
@@ -341,7 +341,7 @@ MJEvalOuterValues(MergeJoinState *mergestate)
static MJEvalResult
MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
{
- ExprContext *econtext = mergestate->mj_InnerEContext;
+ ExprContext *econtext = mergestate->js.ps.ps_ExprContext;
MJEvalResult result = MJEVAL_MATCHABLE;
int i;
MemoryContext oldContext;
@@ -352,7 +352,7 @@ MJEvalInnerValues(MergeJoinState *mergestate, TupleTableSlot *innerslot)
ResetExprContext(econtext);
- oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ oldContext = MemoryContextSwitchTo(mergestate->js.inner_tuple_memory);
econtext->ecxt_innertuple = innerslot;
@@ -1471,14 +1471,6 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
*/
ExecAssignExprContext(estate, &mergestate->js.ps);
- /*
- * we need two additional econtexts in which we can compute the join
- * expressions from the left and right input tuples. The node's regular
- * econtext won't do because it gets reset too often.
- */
- mergestate->mj_OuterEContext = CreateExprContext(estate);
- mergestate->mj_InnerEContext = CreateExprContext(estate);
-
/*
* initialize child nodes
*
@@ -1497,6 +1489,15 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
(eflags | EXEC_FLAG_MARK));
innerDesc = ExecGetResultType(innerPlanState(mergestate));
+ mergestate->js.outer_tuple_memory = AllocSetContextCreate(
+ mergestate->js.ps.ps_ExprContext->ecxt_per_query_memory,
+ "OuterTupleCtx",
+ ALLOCSET_SMALL_SIZES);
+ mergestate->js.inner_tuple_memory = AllocSetContextCreate(
+ mergestate->js.ps.ps_ExprContext->ecxt_per_query_memory,
+ "InnerTupleCtx",
+ ALLOCSET_SMALL_SIZES);
+
/*
* For certain types of inner child nodes, it is advantageous to issue
* MARK every time we advance past an inner tuple we will never return to.
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index ebd1406843..5e10d01c4e 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -349,6 +349,7 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
NL1_printf("ExecInitNestLoop: %s\n",
"node initialized");
+
return nlstate;
}
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5d7f17dee0..c0d4958ba2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2003,6 +2003,8 @@ typedef struct JoinState
bool single_match; /* True if we should skip to next outer tuple
* after finding one inner match */
ExprState *joinqual; /* JOIN quals (in addition to ps.qual) */
+ MemoryContext outer_tuple_memory;
+ MemoryContext inner_tuple_memory;
} JoinState;
/* ----------------
@@ -2064,8 +2066,6 @@ typedef struct MergeJoinState
TupleTableSlot *mj_MarkedTupleSlot;
TupleTableSlot *mj_NullOuterTupleSlot;
TupleTableSlot *mj_NullInnerTupleSlot;
- ExprContext *mj_OuterEContext;
- ExprContext *mj_InnerEContext;
} MergeJoinState;
/* ----------------
--
2.34.1
--
Best Regards
Andy Fan