On 23/01/2026 22:16, Andres Freund wrote:
Hi,

In [1] I was looking at the profile of a seqscan with a where clause that
doesn't match any of the many rows.  I was a bit saddened by where we were
spending time.


- The fetching of variables, as well as the null check of scandesc, in
   SeqNext() is repeated in every loop iteration of ExecScanExtended, despite
   that obviously not being required after the first iteration

   We could perhaps address this by moving the check to the callers of
   ExecScanExtended() or by extending ExecScanExtended to have an explicit
   beginscan callback that it calls after.

For context, we're talking about this in SeqNext:

        /*
         * get information from the estate and scan state
         */
        scandesc = node->ss.ss_currentScanDesc;
        estate = node->ss.ps.state;
        direction = estate->es_direction;
        slot = node->ss.ss_ScanTupleSlot;

Hmm. I guess the compiler doesn't know that the variables don't change between calls, so it has to fetch them on every iteration. Passing them through a 'const' pointer might give it clue, but I'm not sure how to shoehorn that here.

Perhaps we should turn the ExecScanExtended() function inside out. Instead of passing SeqNext as a callback to ExecScanExtended(), we would have a function like this (for illustration purposes only, doesn't compile):

/* ----------------------------------------------------------------
 *              ExecSeqNextInline
 *
 *              This is a workhorse for ExecSeqScan. It's inlined into
 *              specialized implementations for cases where epqstate, qual,
 *              projInfo are NULL or not.
 * ----------------------------------------------------------------
 */
static pg_attribute_always_inline TupleTableSlot *
ExecSeqScanInline(SeqScanState *node,
                                  EPQState *epqstate,
                                  ExprState *qual,
                                  ProjectionInfo *projInfo)
{
        /*
         * get information from the estate and scan state
         */
        scandesc = node->ss.ss_currentScanDesc;
        estate = node->ss.ps.state;
        direction = estate->es_direction;
        slot = node->ss.ss_ScanTupleSlot;

        if (scandesc == NULL)
        {
                /*
                 * We reach here if the scan is not parallel, or if we're 
serially
                 * executing a scan that was planned to be parallel.
                 */
                scandesc = table_beginscan(node->ss.ss_currentRelation,
                                                                   
estate->es_snapshot,
                                                                   0, NULL);
                node->ss.ss_currentScanDesc = scandesc;
        }

        for (;;)
        {
                /* do ExecScanFetch(), except for the call to SeqScanNext */
                slot = ExecScanFetchEPQ(...);

                /*
                 * get the row next tuple from the table
                 */
                if (slot == NULL)
                        slot = table_scan_getnextslot(scandesc, direction, 
slot);

                if (slot == NULL)
                        break;

                /*
                 * Does it pass the quals? (This does the parts of 
ExecScanExtended()
                 * after the ExecScanFetch() call)
                 */
                slot = ExecScanProjectAndFilter(&node->ss, slot, qual, 
projInfo);
                if (slot)
                        return slot;
        }
        return NULL;
}

- Heikki


Reply via email to