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();
 		}

Reply via email to