On Sat, 24 Jan 2026 at 19:21, Amit Langote <[email protected]> wrote:
> On Sat, Jan 24, 2026 at 5:16 AM Andres Freund <[email protected]> wrote:
> > Or perhaps we could just make it so that the entire if (scandesc == NULL)
> > branch isn't needed?
>
> Kind of like ExecProcNodeFirst(), what if we replace the variant
> selection in ExecInitSeqScan() with just:
I imagined moving it to ExecInitSeqScan() and just avoid doing it when
we're doing EXPLAIN or we're doing a parallel scan. Something like the
attached, which is giving me a 4% speedup selecting from a million row
table with a single int column running a seqscan query with a WHERE
clause matching no rows.
> > We should change ExecStoreBufferHeapTuple() to return true. Nobody uses
> > the
> > current return value. Alternatively we should consider just moving it to
> > somewhere heapam.c/heapam_handler.c can see the implementations, they're
> > the
> > only ones that should use it anyway.
>
> Makes sense. Changing ExecStoreBufferHeapTuple() to return true seems
> like the simpler option, unless I misunderstood.
It's probably too late to change it now, but wouldn't it have been
better if scan_getnextslot had been coded to return the TupleTableSlot
rather than bool? That way you could get the sibling call in
ExecStoreBufferHeapTuple() and in SeqNext().
I also noticed my compiler does not inline SeqNext(). Adding a
pg_attribute_always_inline results in it getting inlined and gives a
small speedup.
David
diff --git a/src/backend/executor/nodeSeqscan.c
b/src/backend/executor/nodeSeqscan.c
index b8119face43..87420e60dc9 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -63,17 +63,6 @@ SeqNext(SeqScanState *node)
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;
- }
/*
* get the next tuple from the table
@@ -258,6 +247,21 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
scanstate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+ /*
+ * Build the TableScanDesc unless we're just doing an EXPLAIN without
+ * ANALYZE. Parallel SeqScan's TableScanDesc is built by
+ * ExecSeqScanInitializeDSM or ExecSeqScanInitializeWorker.
+ */
+ if ((eflags & EXEC_FLAG_EXPLAIN_ONLY) == 0 &&
+ node->scan.plan.parallel_aware == false)
+ {
+ scanstate->ss.ss_currentScanDesc =
+ table_beginscan(scanstate->ss.ss_currentRelation,
+ estate->es_snapshot,
+ 0,
+ NULL);
+ }
+
/*
* When EvalPlanQual() is not in use, assign ExecProcNode for this node
* based on the presence of qual and projection. Each ExecSeqScan*()