Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit fb9f955

Browse files
amitlanDavid Rowley
and
David Rowley
committed
Refactor ExecScan() to allow inlining of its core logic
This commit refactors ExecScan() by moving its tuple-fetching, filtering, and projection logic into an inline-able function, ExecScanExtended(), defined in src/include/executor/execScan.h. ExecScanExtended() accepts parameters for EvalPlanQual state, qualifiers (ExprState), and projection (ProjectionInfo). Specialized variants of the execution function of a given Scan node (for example, ExecSeqScan() for SeqScan) can then pass const-NULL for unused parameters. This allows the compiler to inline the logic and eliminate unnecessary branches or checks. Each variant function thus contains only the necessary code, optimizing execution for scans where these features are not needed. The variant function to be used is determined in the ExecInit*() function of the node and assigned to the ExecProcNode function pointer in the node's PlanState, effectively turning runtime checks and conditional branches on the NULLness of epqstate, qual, and projInfo into static ones, provided the compiler successfully eliminates unnecessary checks from the inlined code of ExecScanExtended(). Currently, only ExecSeqScan() is modified to take advantage of this inline-ability. Other Scan nodes might benefit from such specialized variant functions but that is left as future work. Benchmarks performed by Junwang Zhao, David Rowley and myself show up to a 5% reduction in execution time for queries that rely heavily on Seq Scans. The most significant improvements were observed in scenarios where EvalPlanQual, qualifiers, and projection were not required, but other cases also benefit from reduced runtime overhead due to the inlining and removal of unnecessary code paths. The idea for this patch first came from Andres Freund in an off-list discussion. The refactoring approach implemented here is based on a proposal by David Rowley, significantly improving upon the patch I (amitlan) initially proposed. Suggested-by: Andres Freund <andres@anarazel.de> Co-authored-by: David Rowley <dgrowleyml@gmail.com> Reviewed-by: David Rowley <dgrowleyml@gmail.com> Reviewed-by: Junwang Zhao <zhjwpku@gmail.com> Tested-by: Junwang Zhao <zhjwpku@gmail.com> Tested-by: David Rowley <dgrowleyml@gmail.com> Discussion: https://postgr.es/m/CA+HiwqGaH-otvqW_ce-paL=96JvU4j+Xbuk+14esJNDwefdkOg@mail.gmail.com
1 parent 4feba03 commit fb9f955

File tree

3 files changed

+365
-203
lines changed

3 files changed

+365
-203
lines changed

src/backend/executor/execScan.c

Lines changed: 9 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -19,118 +19,9 @@
1919
#include "postgres.h"
2020

2121
#include "executor/executor.h"
22+
#include "executor/execScan.h"
2223
#include "miscadmin.h"
2324

24-
25-
26-
/*
27-
* ExecScanFetch -- check interrupts & fetch next potential tuple
28-
*
29-
* This routine is concerned with substituting a test tuple if we are
30-
* inside an EvalPlanQual recheck. If we aren't, just execute
31-
* the access method's next-tuple routine.
32-
*/
33-
static inline TupleTableSlot *
34-
ExecScanFetch(ScanState *node,
35-
ExecScanAccessMtd accessMtd,
36-
ExecScanRecheckMtd recheckMtd)
37-
{
38-
EState *estate = node->ps.state;
39-
40-
CHECK_FOR_INTERRUPTS();
41-
42-
if (estate->es_epq_active != NULL)
43-
{
44-
EPQState *epqstate = estate->es_epq_active;
45-
46-
/*
47-
* We are inside an EvalPlanQual recheck. Return the test tuple if
48-
* one is available, after rechecking any access-method-specific
49-
* conditions.
50-
*/
51-
Index scanrelid = ((Scan *) node->ps.plan)->scanrelid;
52-
53-
if (scanrelid == 0)
54-
{
55-
/*
56-
* This is a ForeignScan or CustomScan which has pushed down a
57-
* join to the remote side. The recheck method is responsible not
58-
* only for rechecking the scan/join quals but also for storing
59-
* the correct tuple in the slot.
60-
*/
61-
62-
TupleTableSlot *slot = node->ss_ScanTupleSlot;
63-
64-
if (!(*recheckMtd) (node, slot))
65-
ExecClearTuple(slot); /* would not be returned by scan */
66-
return slot;
67-
}
68-
else if (epqstate->relsubs_done[scanrelid - 1])
69-
{
70-
/*
71-
* Return empty slot, as either there is no EPQ tuple for this rel
72-
* or we already returned it.
73-
*/
74-
75-
TupleTableSlot *slot = node->ss_ScanTupleSlot;
76-
77-
return ExecClearTuple(slot);
78-
}
79-
else if (epqstate->relsubs_slot[scanrelid - 1] != NULL)
80-
{
81-
/*
82-
* Return replacement tuple provided by the EPQ caller.
83-
*/
84-
85-
TupleTableSlot *slot = epqstate->relsubs_slot[scanrelid - 1];
86-
87-
Assert(epqstate->relsubs_rowmark[scanrelid - 1] == NULL);
88-
89-
/* Mark to remember that we shouldn't return it again */
90-
epqstate->relsubs_done[scanrelid - 1] = true;
91-
92-
/* Return empty slot if we haven't got a test tuple */
93-
if (TupIsNull(slot))
94-
return NULL;
95-
96-
/* Check if it meets the access-method conditions */
97-
if (!(*recheckMtd) (node, slot))
98-
return ExecClearTuple(slot); /* would not be returned by
99-
* scan */
100-
return slot;
101-
}
102-
else if (epqstate->relsubs_rowmark[scanrelid - 1] != NULL)
103-
{
104-
/*
105-
* Fetch and return replacement tuple using a non-locking rowmark.
106-
*/
107-
108-
TupleTableSlot *slot = node->ss_ScanTupleSlot;
109-
110-
/* Mark to remember that we shouldn't return more */
111-
epqstate->relsubs_done[scanrelid - 1] = true;
112-
113-
if (!EvalPlanQualFetchRowMark(epqstate, scanrelid, slot))
114-
return NULL;
115-
116-
/* Return empty slot if we haven't got a test tuple */
117-
if (TupIsNull(slot))
118-
return NULL;
119-
120-
/* Check if it meets the access-method conditions */
121-
if (!(*recheckMtd) (node, slot))
122-
return ExecClearTuple(slot); /* would not be returned by
123-
* scan */
124-
return slot;
125-
}
126-
}
127-
128-
/*
129-
* Run the node-type-specific access method function to get the next tuple
130-
*/
131-
return (*accessMtd) (node);
132-
}
133-
13425
/* ----------------------------------------------------------------
13526
* ExecScan
13627
*
@@ -157,100 +48,20 @@ ExecScan(ScanState *node,
15748
ExecScanAccessMtd accessMtd, /* function returning a tuple */
15849
ExecScanRecheckMtd recheckMtd)
15950
{
160-
ExprContext *econtext;
51+
EPQState *epqstate;
16152
ExprState *qual;
16253
ProjectionInfo *projInfo;
16354

164-
/*
165-
* Fetch data from node
166-
*/
55+
epqstate = node->ps.state->es_epq_active;
16756
qual = node->ps.qual;
16857
projInfo = node->ps.ps_ProjInfo;
169-
econtext = node->ps.ps_ExprContext;
170-
171-
/* interrupt checks are in ExecScanFetch */
172-
173-
/*
174-
* If we have neither a qual to check nor a projection to do, just skip
175-
* all the overhead and return the raw scan tuple.
176-
*/
177-
if (!qual && !projInfo)
178-
{
179-
ResetExprContext(econtext);
180-
return ExecScanFetch(node, accessMtd, recheckMtd);
181-
}
182-
183-
/*
184-
* Reset per-tuple memory context to free any expression evaluation
185-
* storage allocated in the previous tuple cycle.
186-
*/
187-
ResetExprContext(econtext);
188-
189-
/*
190-
* get a tuple from the access method. Loop until we obtain a tuple that
191-
* passes the qualification.
192-
*/
193-
for (;;)
194-
{
195-
TupleTableSlot *slot;
19658

197-
slot = ExecScanFetch(node, accessMtd, recheckMtd);
198-
199-
/*
200-
* if the slot returned by the accessMtd contains NULL, then it means
201-
* there is nothing more to scan so we just return an empty slot,
202-
* being careful to use the projection result slot so it has correct
203-
* tupleDesc.
204-
*/
205-
if (TupIsNull(slot))
206-
{
207-
if (projInfo)
208-
return ExecClearTuple(projInfo->pi_state.resultslot);
209-
else
210-
return slot;
211-
}
212-
213-
/*
214-
* place the current tuple into the expr context
215-
*/
216-
econtext->ecxt_scantuple = slot;
217-
218-
/*
219-
* check that the current tuple satisfies the qual-clause
220-
*
221-
* check for non-null qual here to avoid a function call to ExecQual()
222-
* when the qual is null ... saves only a few cycles, but they add up
223-
* ...
224-
*/
225-
if (qual == NULL || ExecQual(qual, econtext))
226-
{
227-
/*
228-
* Found a satisfactory scan tuple.
229-
*/
230-
if (projInfo)
231-
{
232-
/*
233-
* Form a projection tuple, store it in the result tuple slot
234-
* and return it.
235-
*/
236-
return ExecProject(projInfo);
237-
}
238-
else
239-
{
240-
/*
241-
* Here, we aren't projecting, so just return scan tuple.
242-
*/
243-
return slot;
244-
}
245-
}
246-
else
247-
InstrCountFiltered1(node, 1);
248-
249-
/*
250-
* Tuple fails qual, so free per-tuple memory and try again.
251-
*/
252-
ResetExprContext(econtext);
253-
}
59+
return ExecScanExtended(node,
60+
accessMtd,
61+
recheckMtd,
62+
epqstate,
63+
qual,
64+
projInfo);
25465
}
25566

25667
/*

src/backend/executor/nodeSeqscan.c

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#include "access/relscan.h"
3131
#include "access/tableam.h"
32+
#include "executor/execScan.h"
3233
#include "executor/executor.h"
3334
#include "executor/nodeSeqscan.h"
3435
#include "utils/rel.h"
@@ -99,22 +100,105 @@ SeqRecheck(SeqScanState *node, TupleTableSlot *slot)
99100
* ExecSeqScan(node)
100101
*
101102
* Scans the relation sequentially and returns the next qualifying
102-
* tuple.
103-
* We call the ExecScan() routine and pass it the appropriate
104-
* access method functions.
103+
* tuple. This variant is used when there is no es_eqp_active, no qual
104+
* and no projection. Passing const-NULLs for these to ExecScanExtended
105+
* allows the compiler to eliminate the additional code that would
106+
* ordinarily be required for the evaluation of these.
105107
* ----------------------------------------------------------------
106108
*/
107109
static TupleTableSlot *
108110
ExecSeqScan(PlanState *pstate)
109111
{
110112
SeqScanState *node = castNode(SeqScanState, pstate);
111113

114+
Assert(pstate->state->es_epq_active == NULL);
115+
Assert(pstate->qual == NULL);
116+
Assert(pstate->ps_ProjInfo == NULL);
117+
118+
return ExecScanExtended(&node->ss,
119+
(ExecScanAccessMtd) SeqNext,
120+
(ExecScanRecheckMtd) SeqRecheck,
121+
NULL,
122+
NULL,
123+
NULL);
124+
}
125+
126+
/*
127+
* Variant of ExecSeqScan() but when qual evaluation is required.
128+
*/
129+
static TupleTableSlot *
130+
ExecSeqScanWithQual(PlanState *pstate)
131+
{
132+
SeqScanState *node = castNode(SeqScanState, pstate);
133+
134+
Assert(pstate->state->es_epq_active == NULL);
135+
Assert(pstate->qual != NULL);
136+
Assert(pstate->ps_ProjInfo == NULL);
137+
138+
return ExecScanExtended(&node->ss,
139+
(ExecScanAccessMtd) SeqNext,
140+
(ExecScanRecheckMtd) SeqRecheck,
141+
NULL,
142+
pstate->qual,
143+
NULL);
144+
}
145+
146+
/*
147+
* Variant of ExecSeqScan() but when projection is required.
148+
*/
149+
static TupleTableSlot *
150+
ExecSeqScanWithProject(PlanState *pstate)
151+
{
152+
SeqScanState *node = castNode(SeqScanState, pstate);
153+
154+
Assert(pstate->state->es_epq_active == NULL);
155+
Assert(pstate->qual == NULL);
156+
Assert(pstate->ps_ProjInfo != NULL);
157+
158+
return ExecScanExtended(&node->ss,
159+
(ExecScanAccessMtd) SeqNext,
160+
(ExecScanRecheckMtd) SeqRecheck,
161+
NULL,
162+
NULL,
163+
pstate->ps_ProjInfo);
164+
}
165+
166+
/*
167+
* Variant of ExecSeqScan() but when qual evaluation and projection are
168+
* required.
169+
*/
170+
static TupleTableSlot *
171+
ExecSeqScanWithQualProject(PlanState *pstate)
172+
{
173+
SeqScanState *node = castNode(SeqScanState, pstate);
174+
175+
Assert(pstate->state->es_epq_active == NULL);
176+
Assert(pstate->qual != NULL);
177+
Assert(pstate->ps_ProjInfo != NULL);
178+
179+
return ExecScanExtended(&node->ss,
180+
(ExecScanAccessMtd) SeqNext,
181+
(ExecScanRecheckMtd) SeqRecheck,
182+
NULL,
183+
pstate->qual,
184+
pstate->ps_ProjInfo);
185+
}
186+
187+
/*
188+
* Variant of ExecSeqScan for when EPQ evaluation is required. We don't
189+
* bother adding variants of this for with/without qual and projection as
190+
* EPQ doesn't seem as exciting a case to optimize for.
191+
*/
192+
static TupleTableSlot *
193+
ExecSeqScanEPQ(PlanState *pstate)
194+
{
195+
SeqScanState *node = castNode(SeqScanState, pstate);
196+
112197
return ExecScan(&node->ss,
113198
(ExecScanAccessMtd) SeqNext,
114199
(ExecScanRecheckMtd) SeqRecheck);
115200
}
116201

117-
118202
/* ----------------------------------------------------------------
119203
* ExecInitSeqScan
120204
* ----------------------------------------------------------------
@@ -137,7 +221,6 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
137221
scanstate = makeNode(SeqScanState);
138222
scanstate->ss.ps.plan = (Plan *) node;
139223
scanstate->ss.ps.state = estate;
140-
scanstate->ss.ps.ExecProcNode = ExecSeqScan;
141224

142225
/*
143226
* Miscellaneous initialization
@@ -171,6 +254,28 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
171254
scanstate->ss.ps.qual =
172255
ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
173256

257+
/*
258+
* When EvalPlanQual() is not in use, assign ExecProcNode for this node
259+
* based on the presence of qual and projection. Each ExecSeqScan*()
260+
* variant is optimized for the specific combination of these conditions.
261+
*/
262+
if (scanstate->ss.ps.state->es_epq_active != NULL)
263+
scanstate->ss.ps.ExecProcNode = ExecSeqScanEPQ;
264+
else if (scanstate->ss.ps.qual == NULL)
265+
{
266+
if (scanstate->ss.ps.ps_ProjInfo == NULL)
267+
scanstate->ss.ps.ExecProcNode = ExecSeqScan;
268+
else
269+
scanstate->ss.ps.ExecProcNode = ExecSeqScanWithProject;
270+
}
271+
else
272+
{
273+
if (scanstate->ss.ps.ps_ProjInfo == NULL)
274+
scanstate->ss.ps.ExecProcNode = ExecSeqScanWithQual;
275+
else
276+
scanstate->ss.ps.ExecProcNode = ExecSeqScanWithQualProject;
277+
}
278+
174279
return scanstate;
175280
}
176281

0 commit comments

Comments
 (0)