From 86d15e8f2f5138ffa61ae57947baf10077b91563 Mon Sep 17 00:00:00 2001 From: "dgrowley@gmail.com" Date: Thu, 23 Aug 2018 18:48:21 +1200 Subject: [PATCH v1] Change the executor's range table from a List to an array When the range table contained many entries performing an rt_fetch or a getrelid could become quite expensive due to List's O(n) lookup time. With an array the lookup is O(1). This improves performance quite a bit for partitioned table UPDATEs and DELETEs which have many result relations. When looking for a RangeTblEntry in the executor now the new exec_rt_fetch macro must be used. rt_fetch is no longer valid. --- contrib/postgres_fdw/postgres_fdw.c | 12 ++++++------ src/backend/commands/copy.c | 12 +++++++++++- src/backend/commands/trigger.c | 2 +- src/backend/executor/execExprInterp.c | 6 +++--- src/backend/executor/execMain.c | 29 +++++++++++++++++++++-------- src/backend/executor/execParallel.c | 10 +++++++++- src/backend/executor/execUtils.c | 7 ++++--- src/backend/executor/nodeLockRows.c | 2 +- src/backend/replication/logical/worker.c | 3 ++- src/include/nodes/execnodes.h | 19 ++++++++++++++++++- src/include/parser/parsetree.h | 10 ---------- 11 files changed, 76 insertions(+), 36 deletions(-) diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 0803c30a48..2fb3efaf3c 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -1345,7 +1345,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags) rtindex = fsplan->scan.scanrelid; else rtindex = bms_next_member(fsplan->fs_relids, -1); - rte = rt_fetch(rtindex, estate->es_range_table); + rte = exec_rt_fetch(rtindex, estate->es_range_table_array); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); /* Get info about foreign table. */ @@ -1732,8 +1732,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate, FdwModifyPrivateRetrievedAttrs); /* Find RTE. */ - rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, - mtstate->ps.state->es_range_table); + rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex, + mtstate->ps.state->es_range_table_array); /* Construct an execution state. */ fmstate = create_foreign_modify(mtstate->ps.state, @@ -2037,7 +2037,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate, * correspond to this partition if it is one of the UPDATE subplan target * rels; in that case, we can just use the existing RTE as-is. */ - rte = list_nth(estate->es_range_table, resultRelation - 1); + rte = exec_rt_fetch(resultRelation, estate->es_range_table_array); if (rte->relid != RelationGetRelid(rel)) { rte = copyObject(rte); @@ -2397,7 +2397,7 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags) * ExecCheckRTEPerms() does. */ rtindex = estate->es_result_relation_info->ri_RangeTableIndex; - rte = rt_fetch(rtindex, estate->es_range_table); + rte = exec_rt_fetch(rtindex, estate->es_range_table_array); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); /* Get info about foreign table. */ @@ -5757,7 +5757,7 @@ conversion_error_callback(void *arg) RangeTblEntry *rte; Var *var = (Var *) tle->expr; - rte = rt_fetch(var->varno, estate->es_range_table); + rte = exec_rt_fetch(var->varno, estate->es_range_table_array); if (var->varattno == 0) is_wholerow = true; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 9bc67ce60f..f3d423b8a1 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2333,6 +2333,8 @@ CopyFrom(CopyState cstate) bool has_before_insert_row_trig; bool has_instead_insert_row_trig; bool leafpart_use_multi_insert = false; + int i; + ListCell *lc; #define MAX_BUFFERED_TUPLES 1000 #define RECHECK_MULTI_INSERT_THRESHOLD 1000 @@ -2482,7 +2484,15 @@ CopyFrom(CopyState cstate) estate->es_result_relations = resultRelInfo; estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; - estate->es_range_table = cstate->range_table; + + estate->es_range_table_size = list_length(cstate->range_table); + estate->es_range_table_array = (RangeTblEntry **) palloc(sizeof(RangeTblEntry *) * + estate->es_range_table_size); + + /* Populate the range table array */ + i = 0; + foreach(lc, cstate->range_table) + estate->es_range_table_array[i++] = lfirst_node(RangeTblEntry, lc); /* Set up a tuple slot too */ myslot = ExecInitExtraTupleSlot(estate, tupDesc); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 2436692eb8..6184e21878 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -75,7 +75,7 @@ static int MyTriggerDepth = 0; * to be changed, however. */ #define GetUpdatedColumns(relinfo, estate) \ - (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols) + (exec_rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table_array)->updatedCols) /* Local function prototypes */ static void ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 9d6e25aae5..9a5589552a 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -3961,10 +3961,10 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext) * perhaps other places.) */ if (econtext->ecxt_estate && - variable->varno <= list_length(econtext->ecxt_estate->es_range_table)) + variable->varno <= econtext->ecxt_estate->es_range_table_size) { - RangeTblEntry *rte = rt_fetch(variable->varno, - econtext->ecxt_estate->es_range_table); + RangeTblEntry *rte = exec_rt_fetch(variable->varno, + econtext->ecxt_estate->es_range_table_array); if (rte->eref) ExecTypeSetColNames(output_tupdesc, rte->eref->colnames); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index c583e020a0..c8dec6722d 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -107,9 +107,9 @@ static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, * to be changed, however. */ #define GetInsertedColumns(relinfo, estate) \ - (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols) + (exec_rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table_array)->insertedCols) #define GetUpdatedColumns(relinfo, estate) \ - (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols) + (exec_rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table_array)->updatedCols) /* end of local decls */ @@ -806,7 +806,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) CmdType operation = queryDesc->operation; PlannedStmt *plannedstmt = queryDesc->plannedstmt; Plan *plan = plannedstmt->planTree; - List *rangeTable = plannedstmt->rtable; + RangeTblEntry **rangeTable; EState *estate = queryDesc->estate; PlanState *planstate; TupleDesc tupType; @@ -816,12 +816,24 @@ InitPlan(QueryDesc *queryDesc, int eflags) /* * Do permissions checks */ - ExecCheckRTPerms(rangeTable, true); + ExecCheckRTPerms(plannedstmt->rtable, true); /* * initialize the node's execution state */ - estate->es_range_table = rangeTable; + + /* Build the range table array from the rtable List */ + estate->es_range_table_size = list_length(plannedstmt->rtable); + rangeTable = (RangeTblEntry **) palloc(sizeof(RangeTblEntry *) * + estate->es_range_table_size); + estate->es_range_table_array = rangeTable; + + /* Populate the range table array */ + i = 0; + foreach(l, plannedstmt->rtable) + estate->es_range_table_array[i++] = lfirst_node(RangeTblEntry, l); + + estate->es_plannedstmt = plannedstmt; /* @@ -3068,7 +3080,7 @@ EvalPlanQualBegin(EPQState *epqstate, EState *parentestate) /* * We already have a suitable child EPQ tree, so just reset it. */ - int rtsize = list_length(parentestate->es_range_table); + int rtsize = parentestate->es_range_table_size; PlanState *planstate = epqstate->planstate; MemSet(estate->es_epqScanDone, 0, rtsize * sizeof(bool)); @@ -3113,7 +3125,7 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree) MemoryContext oldcontext; ListCell *l; - rtsize = list_length(parentestate->es_range_table); + rtsize = parentestate->es_range_table_size; epqstate->estate = estate = CreateExecutorState(); @@ -3136,7 +3148,8 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree) estate->es_direction = ForwardScanDirection; estate->es_snapshot = parentestate->es_snapshot; estate->es_crosscheck_snapshot = parentestate->es_crosscheck_snapshot; - estate->es_range_table = parentestate->es_range_table; + estate->es_range_table_array = parentestate->es_range_table_array; + estate->es_range_table_size = parentestate->es_range_table_size; estate->es_plannedstmt = parentestate->es_plannedstmt; estate->es_junkFilter = parentestate->es_junkFilter; estate->es_output_cid = parentestate->es_output_cid; diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index ee0f07a81e..48bb965bd3 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -144,6 +144,8 @@ ExecSerializePlan(Plan *plan, EState *estate) { PlannedStmt *pstmt; ListCell *lc; + List *rtable; + int i; /* We can't scribble on the original plan, so make a copy. */ plan = copyObject(plan); @@ -179,7 +181,13 @@ ExecSerializePlan(Plan *plan, EState *estate) pstmt->dependsOnRole = false; pstmt->parallelModeNeeded = false; pstmt->planTree = plan; - pstmt->rtable = estate->es_range_table; + + /* Transform the range table array into List form */ + rtable = NIL; + for (i = 0; i < estate->es_range_table_size; i++) + rtable = lappend(rtable, estate->es_range_table_array[i]); + + pstmt->rtable = rtable; pstmt->resultRelations = NIL; pstmt->nonleafResultRelations = NIL; diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 5b3eaec80b..099f0fe5c6 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -105,7 +105,8 @@ CreateExecutorState(void) estate->es_direction = ForwardScanDirection; estate->es_snapshot = InvalidSnapshot; /* caller must initialize this */ estate->es_crosscheck_snapshot = InvalidSnapshot; /* no crosscheck */ - estate->es_range_table = NIL; + estate->es_range_table_array = NULL; + estate->es_range_table_size = 0; estate->es_plannedstmt = NULL; estate->es_junkFilter = NULL; @@ -672,7 +673,7 @@ ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags) } /* Open the relation and acquire lock as needed */ - reloid = getrelid(scanrelid, estate->es_range_table); + reloid = getrelid(scanrelid, estate->es_range_table_array); rel = heap_open(reloid, lockmode); /* @@ -881,7 +882,7 @@ ExecLockNonLeafAppendTables(List *partitioned_rels, EState *estate) ListCell *l; Index rti = lfirst_int(lc); bool is_result_rel = false; - Oid relid = getrelid(rti, estate->es_range_table); + Oid relid = getrelid(rti, estate->es_range_table_array); /* If this is a result relation, already locked in InitPlan */ foreach(l, stmt->nonleafResultRelations) diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 30de8a95ab..6db345ae7a 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -400,7 +400,7 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags) /* * Create workspace in which we can remember per-RTE locked tuples */ - lrstate->lr_ntables = list_length(estate->es_range_table); + lrstate->lr_ntables = estate->es_range_table_size; lrstate->lr_curtuples = (HeapTuple *) palloc0(lrstate->lr_ntables * sizeof(HeapTuple)); diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 2054abe653..5e7aa7c6c5 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -199,7 +199,8 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel) rte->rtekind = RTE_RELATION; rte->relid = RelationGetRelid(rel->localrel); rte->relkind = rel->localrel->rd_rel->relkind; - estate->es_range_table = list_make1(rte); + estate->es_range_table_array = &rte; + estate->es_range_table_size = 1; resultRelInfo = makeNode(ResultRelInfo); InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 41fa2052a2..c9e5194056 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -477,7 +477,8 @@ typedef struct EState ScanDirection es_direction; /* current scan direction */ Snapshot es_snapshot; /* time qual to use */ Snapshot es_crosscheck_snapshot; /* crosscheck time qual for RI */ - List *es_range_table; /* List of RangeTblEntry */ + struct RangeTblEntry **es_range_table_array; /* 0-based range table */ + int es_range_table_size; /* size of the range table array */ PlannedStmt *es_plannedstmt; /* link to top of plan tree */ const char *es_sourceText; /* Source text from QueryDesc */ @@ -573,6 +574,22 @@ typedef struct EState struct JitContext *es_jit; } EState; +/* + * exec_rt_fetch + * + * NB: this will crash and burn if handed an out-of-range RT index + */ +#define exec_rt_fetch(rangetblidx, rangetbl) rangetbl[(rangetblidx) - 1] + +/* XXX moved from parsetree.h. okay?? + * getrelid + * + * Given the range index of a relation, return the corresponding + * relation OID. Note that InvalidOid will be returned if the + * RTE is for a non-relation-type RTE. + */ +#define getrelid(rangeindex,rangetable) \ + (exec_rt_fetch(rangeindex, rangetable)->relid) /* * ExecRowMark - diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h index dd9ae658ac..fe16d7d1fa 100644 --- a/src/include/parser/parsetree.h +++ b/src/include/parser/parsetree.h @@ -31,16 +31,6 @@ #define rt_fetch(rangetable_index, rangetable) \ ((RangeTblEntry *) list_nth(rangetable, (rangetable_index)-1)) -/* - * getrelid - * - * Given the range index of a relation, return the corresponding - * relation OID. Note that InvalidOid will be returned if the - * RTE is for a non-relation-type RTE. - */ -#define getrelid(rangeindex,rangetable) \ - (rt_fetch(rangeindex, rangetable)->relid) - /* * Given an RTE and an attribute number, return the appropriate * variable name or alias for that attribute of that RTE. -- 2.16.2.windows.1