From 4916a0891b2e7176dee3c2a3a8018a4d174dd373 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Thu, 29 Jan 2026 05:03:55 +0900 Subject: [PATCH v5 5/5] WIP: Use dedicated interpreter for batched qual evaluation Move batch-related opcodes (EEOP_SCAN_FETCHSOME_BATCH, EEOP_QUAL_BATCH_INITMASK, EEOP_QUAL_BATCH_TERM) out of the main ExecInterpExpr switch and into a dedicated ExecInterpQualBatch function. Adding opcodes to ExecInterpExpr may affect performance even when they are not executed, possibly due to changes in register allocation, jump table layout, or code size. Use a separate interpreter to avoid any risk of impacting the existing per-tuple evaluation path. The batched qual program has a simple linear structure (fetch -> initmask -> term* -> done) that doesn't need computed goto dispatch anyway. --- src/backend/executor/execExprInterp.c | 72 +++++++++++++++++---------- src/backend/executor/nodeSeqscan.c | 6 +-- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 304c7f4e0fb..04a40ec932c 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -189,6 +189,8 @@ static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate int setno); static char *ExecGetJsonValueItemString(JsonbValue *item, bool *resnull); +static Datum ExecInterpQualBatch(ExprState *state, ExprContext *econtext); + /* * ScalarArrayOpExprHashEntry * Hash table entry type used during EEOP_HASHED_SCALARARRAYOP @@ -266,6 +268,12 @@ ExecReadyInterpretedExpr(ExprState *state) */ state->evalfunc = ExecInterpExprStillValid; + if (state->batch_private) + { + state->evalfunc_private = (void *) ExecInterpQualBatch; + return; + } + /* DIRECT_THREADED should not already be set */ Assert((state->flags & EEO_FLAG_DIRECT_THREADED) == 0); @@ -467,7 +475,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) TupleTableSlot *scanslot; TupleTableSlot *oldslot; TupleTableSlot *newslot; - TupleBatch *scanbatch; /* * This array has to be in the same order as enum ExprEvalOp. @@ -594,9 +601,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_AGG_PRESORTED_DISTINCT_MULTI, &&CASE_EEOP_AGG_ORDERED_TRANS_DATUM, &&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE, - &&CASE_EEOP_SCAN_FETCHSOME_BATCH, - &&CASE_EEOP_QUAL_BATCH_INITMASK, - &&CASE_EEOP_QUAL_BATCH_TERM, + &&CASE_EEOP_BATCH_UNREACHABLE, /* EEOP_SCAN_FETCHSOME_BATCH */ + &&CASE_EEOP_BATCH_UNREACHABLE, /* EEOP_QUAL_BATCH_INITMASK */ + &&CASE_EEOP_BATCH_UNREACHABLE, /* EEOP_QUAL_BATCH_TERM */ &&CASE_EEOP_LAST }; @@ -617,7 +624,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) scanslot = econtext->ecxt_scantuple; oldslot = econtext->ecxt_oldtuple; newslot = econtext->ecxt_newtuple; - scanbatch = econtext->scan_batch; #if defined(EEO_USE_COMPUTED_GOTO) EEO_DISPATCH(); @@ -2271,34 +2277,18 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } - EEO_CASE(EEOP_SCAN_FETCHSOME_BATCH) - { - CheckOpSlotCompatibility(op, scanslot); - - Assert(scanbatch); - slot_getsomeattrs_batch(scanbatch, op->d.fetch_batch.last_var); - - EEO_NEXT(); - } - - EEO_CASE(EEOP_QUAL_BATCH_INITMASK) - { - ExecQualBatchInitMask(state, op, econtext); - EEO_NEXT(); - } - - EEO_CASE(EEOP_QUAL_BATCH_TERM) - { - ExecQualBatchTerm(state, op, econtext); - EEO_NEXT(); - } - EEO_CASE(EEOP_LAST) { /* unreachable */ Assert(false); goto out_error; } + + EEO_CASE(EEOP_BATCH_UNREACHABLE) + { + Assert(false && "batch opcodes use dedicated interpreter"); + pg_unreachable(); + } } out_error: @@ -6089,6 +6079,34 @@ ExecQualBatchTerm(ExprState *state, ExprEvalStep *op, ExprContext *econtext) } } +static Datum +ExecInterpQualBatch(ExprState *state, ExprContext *econtext) +{ + ExprEvalStep *op = state->steps; + TupleBatch *scanbatch = econtext->scan_batch; + + /* Step 1: fetch/deform all slots */ + Assert(ExecEvalStepOp(state, op) == EEOP_SCAN_FETCHSOME_BATCH); + slot_getsomeattrs_batch(scanbatch, op->d.fetch_batch.last_var); + op++; + + /* Step 2: initialize mask */ + Assert(ExecEvalStepOp(state, op) == EEOP_QUAL_BATCH_INITMASK); + ExecQualBatchInitMask(state, op, econtext); + op++; + + /* Step 3: process all TERM steps */ + while (ExecEvalStepOp(state, op) == EEOP_QUAL_BATCH_TERM) + { + ExecQualBatchTerm(state, op, econtext); + op++; + } + + Assert(ExecEvalStepOp(state, op) == EEOP_DONE_NO_RETURN); + + return (Datum) 0; +} + /* * ExecQualBatch * Evaluate a batched qual over all rows in a TupleBatch. diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c index 16f15ed68aa..4a76108bd2f 100644 --- a/src/backend/executor/nodeSeqscan.c +++ b/src/backend/executor/nodeSeqscan.c @@ -404,7 +404,6 @@ SeqScanState * ExecInitSeqScan(SeqScan *node, EState *estate, int eflags) { SeqScanState *scanstate; - bool use_batching; /* * Once upon a time it was possible to have an outerPlan of a SeqScan, but @@ -435,12 +434,9 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags) node->scan.scanrelid, eflags); - use_batching = ScanCanUseBatching(&scanstate->ss, eflags); - /* and create slot with the appropriate rowtype */ ExecInitScanTupleSlot(estate, &scanstate->ss, RelationGetDescr(scanstate->ss.ss_currentRelation), - use_batching ? &TTSOpsHeapTuple : table_slot_callbacks(scanstate->ss.ss_currentRelation)); /* @@ -477,7 +473,7 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags) scanstate->ss.ps.ExecProcNode = ExecSeqScanWithQualProject; } - if (use_batching) + if (ScanCanUseBatching(&scanstate->ss, eflags)) SeqScanInitBatching(scanstate, eflags); return scanstate; -- 2.47.3