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