Hi
Main goal of this patch is to avoid repeated calls of immutable/stable
functions.
This patch is against version 10.10.
I guess same logic could be implemented up till version 12.
--- src/include/nodes/execnodes.h 2019-08-05 23:16:54.000000000 +0200
+++ src/include/nodes/execnodes.h 2019-11-03 20:05:34.338305825 +0100
@@ -882,6 +883,39 @@ typedef struct PlanState
TupleTableSlot *ps_ResultTupleSlot; /* slot for my result tuples */
ExprContext *ps_ExprContext; /* node's expression-evaluation context */
ProjectionInfo *ps_ProjInfo; /* info for doing tuple projection */
+#ifdef OPTFUNCALLS
+ /* was_called - list of ExprEvalStep* or FuncExpr* depending on execution stage
+ *
+ * Stage I. ExecInitExprRec()
+ * List gathers all not volatile, not set returning, not window FuncExpr*,
+ * equal nodes occupy one position in the list. Position in this list ( counting from 1 )
+ * and planstate are remembered in actual ExprEvalStep*
+ *
+ * For query: select f(n),f(n) from t - was_called->length will be 1 and ptr_value
+ * will be FuncExpr* node of f(n)
+ *
+ * For query: select f(n),g(n),f(n) from t - list->length == 2
+ *
+ * Stage II. ExecProcnode()
+ * For every planstate->was_called list changes its interpretation - from now on
+ * it is a list of ExprEvalStep* . Before executing real execProcnode
+ * every element of this list ( ptr_value ) is set to NULL. We don't know which
+ * function will be called first
+ *
+ * Stage III. ExecInterpExpr() case EEOP_FUNCEXPR
+ * ExprEvalStep.position > 0 means that in planstate->was_called could be ExprEvalStep*
+ * which was done yet or NULL.
+ *
+ * NULL means that eval step is entered first time and:
+ * 1. real function must be called
+ * 2. ExprEvalStep has to be remembered in planstate->was_called at position
+ * step->position - 1
+ *
+ * NOT NULL means that in planstate->was_called is ExprEvalStep* with ready result, so
+ * there is no need to call function
+ */
+ List *was_called;
+#endif
} PlanState;
/* ----------------
--- src/include/executor/execExpr.h 2019-08-05 23:16:54.000000000 +0200
+++ src/include/executor/execExpr.h 2019-11-03 20:04:03.739025142 +0100
@@ -561,6 +561,10 @@ typedef struct ExprEvalStep
AlternativeSubPlanState *asstate;
} alternative_subplan;
} d;
+#ifdef OPTFUNCALLS
+ PlanState *planstate; /* parent PlanState for this expression */
+ int position; /* position in planstate->was_called counted from 1 */
+#endif
} ExprEvalStep;
--- src/backend/executor/execProcnode.c 2019-08-05 23:16:54.000000000 +0200
+++ src/backend/executor/execProcnode.c 2019-11-03 19:54:28.071672386 +0100
@@ -120,6 +120,17 @@
static TupleTableSlot *ExecProcNodeFirst(PlanState *node);
static TupleTableSlot *ExecProcNodeInstr(PlanState *node);
+#ifdef OPTFUNCALLS
+static TupleTableSlot *execReal(PlanState *node)
+{
+ /* Before each scan step, node->was_called elements must be set to NULL */
+ ListCell *item;
+ foreach(item,node->was_called)
+ item->data.ptr_value = NULL;
+
+ return node->ExecProcNodeReal(node);
+}
+#endif
/* ------------------------------------------------------------------------
* ExecInitNode
@@ -425,8 +436,11 @@ ExecProcNodeFirst(PlanState *node)
if (node->instrument)
node->ExecProcNode = ExecProcNodeInstr;
else
+#ifndef OPTFUNCALLS
node->ExecProcNode = node->ExecProcNodeReal;
-
+#else
+ node->ExecProcNode = execReal;
+#endif
return node->ExecProcNode(node);
}
@@ -442,9 +456,11 @@ ExecProcNodeInstr(PlanState *node)
TupleTableSlot *result;
InstrStartNode(node->instrument);
-
+#ifndef OPTFUNCALLS
result = node->ExecProcNodeReal(node);
-
+#else
+ result = execReal(node);
+#endif
InstrStopNode(node->instrument, TupIsNull(result) ? 0.0 : 1.0);
return result;
--- src/backend/executor/execExpr.c 2019-08-05 23:16:54.000000000 +0200
+++ src/backend/executor/execExpr.c 2019-11-03 19:57:21.994249398 +0100
@@ -45,7 +45,13 @@
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
-
+#ifdef OPTFUNCALLS
+#include "catalog/pg_proc.h"
+#include "utils/syscache.h"
+#include "access/htup_details.h"
+static bool isNotVolatile(Oid funcid);
+static int findFuncExpr(FuncExpr* node,PlanState* parent);
+#endif
typedef struct LastAttnumInfo
{
@@ -806,7 +812,40 @@ ExecInitExprRec(Expr *node, PlanState *p
ExecInitFunc(&scratch, node,
func->args, func->funcid, func->inputcollid,
parent, state);
+#ifdef OPTFUNCALLS
+ scratch.position = 0;
+ scratch.planstate = parent;
+ if( parent )
+ {
+ /* Build/extend the list of non volatile functions for this PlanState node.
+ * Try to find func ( equal node ) in parent->was_called list at first
+ */
+ int pos = findFuncExpr(func,parent);
+ if( !pos && isNotVolatile(func->funcid ))
+ {
+ /* Function is not in the list yet but it's maybe useful for optimizing
+ * repeated calls - register function in parent and remember its position
+ */
+ parent->was_called = lappend(parent->was_called,func);
+ scratch.position = parent->was_called->length;
+ }
+ else if( pos )
+ /* It is repeated call. Identical function is in planstate->was_called
+ * remember its position.
+ */
+ scratch.position = pos;
+
+ /* After ExecInitExprRec of all nodes each PlanState has initialized
+ * was_called list. In cells of these lists are FuncExpr nodes
+ * BUT THESE NODES ARE NOT NEEDED ANYMORE, we need only preallocated list
+ * in parent. Rest information is in ExprEvalStep ( EVStep for short ) position
+ * and planstate == parent. EVSteps with the same position are kind of family that
+ * has one common result after evaluation only one member.
+ */
+ }
+#endif
ExprEvalPushStep(state, &scratch);
+
break;
}
@@ -2696,3 +2735,48 @@ ExecInitCoerceToDomain(ExprEvalStep *scr
}
}
}
+
+#ifdef OPTFUNCALLS
+/* Well, this function must exist till the moment when developers decide, that struct FuncExpr
+ * should also have provolatile field
+ */
+static bool isNotVolatile(Oid funcid)
+{
+ HeapTuple func_tuple;
+ Form_pg_proc func_form;
+ bool result;
+
+ func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(func_tuple))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+ func_form = (Form_pg_proc) GETSTRUCT(func_tuple);
+
+ if( !(func_form->proisagg || func_form->proiswindow || func_form->proretset ) && func_form->provolatile != PROVOLATILE_VOLATILE )
+ result = true;
+ else
+ result = false;
+
+ ReleaseSysCache(func_tuple);
+
+ return result;
+}
+
+
+/* Find FuncExpr in PlanState.was_called list
+ * comparing nodes not node pointers
+ */
+static int findFuncExpr(FuncExpr* node,PlanState* parent)
+{
+ ListCell *item;
+ int i = 0;
+ foreach(item,parent->was_called)
+ {
+ FuncExpr *fe = (FuncExpr*)lfirst(item);
+ i++;
+
+ if( equal(node, fe ) )
+ return i;
+ }
+ return 0;
+}
+#endif
--- src/backend/executor/execExprInterp.c 2019-08-05 23:16:54.000000000 +0200
+++ src/backend/executor/execExprInterp.c 2019-11-03 19:56:32.906648836 +0100
@@ -644,12 +644,33 @@ ExecInterpExpr(ExprState *state, ExprCon
*/
EEO_CASE(EEOP_FUNCEXPR)
{
- FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
-
- fcinfo->isnull = false;
- *op->resvalue = (op->d.func.fn_addr) (fcinfo);
- *op->resnull = fcinfo->isnull;
+#ifdef OPTFUNCALLS
+ /* Check if this is first call of a function ( not window function and not returning set ) */
+ ExprEvalStep* done;
+ if( op->position && (done = (ExprEvalStep*)list_nth(op->planstate->was_called,op->position - 1)) )
+ {
+ /* it is repeated call, so get result from done */
+ *op->resvalue = *done->resvalue;
+ *op->resnull = *done->resnull;
+ }
+ else
+ {
+#endif
+ /* it is first call of function which can be optimized ( position > 0 )
+ * or first/next call of function which can't be optimized ( position == 0 )
+ * call real function
+ */
+ FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+ fcinfo->isnull = false;
+ *op->resvalue = (op->d.func.fn_addr) (fcinfo);
+ *op->resnull = fcinfo->isnull;
+#ifdef OPTFUNCALLS
+ if( op->position )
+ /* be the source of result for other functions having position == op->position */
+ list_nth_cell(op->planstate->was_called,op->position - 1)->data.ptr_value = op;
+ }
+#endif
EEO_NEXT();
}