8000 Refactor ExecScan() to allow inlining of its core logic · postgrespro/postgres@fb9f955 · GitHub
[go: up one dir, main page]

Skip to content
  • Commit fb9f955

    Browse files
    amitlanDavid Rowley
    andcommitted
    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)
    0