Hi, I continued the heap-only experiment I described earlier in this thread and attached v2 as a two-patch series. This is an alternative implementation of that experiment; it does not replace or supersede Amit's v8 series.
I think a batched executor does not necessarily require a new API for passing batches between plan nodes. Not every node will benefit from batching; the most promising nodes are those that process a large stream of tuples. A sequential scan is a natural first candidate. Since heapam is currently the only table AM implementation shipped in core, these patches keep the experiment local to heap scans. The first patch uses information that heap page mode already collects. After heapgettup_pagemode() finds the visible offsets on a page, the buffer heap slot records that page-local batch. heap_getnextslot() can then advance through the remaining offsets without another call to heapgettup_pagemode(). The scan still returns one ordinary slot at a time to its parent, so this changes neither the table AM API nor the interface between plan nodes. The second patch uses the same page batch to evaluate a supported initial prefix of SeqScan quals over as many as 64 tuples. It gathers Datums in a tight loop and then calls the existing operator functions in another tight loop. A 64-bit mask records passing rows. With multiple supported quals, each qual narrows the mask, and later quals are called only for rows that passed the earlier ones. This is not SIMD. The gain comes from better locality and from doing less executor work between adjacent operator calls. An important consequence is that operators need no new batch or SIMD-specific API; the code calls their existing scalar functions and also supports user types and cross-type operators. The batch path currently accepts binary OpExpr clauses of these forms: Var op Const/Param Const/Param op Var Here Param means PARAM_EXTERN or PARAM_EXEC. The operator must return bool, must not return a set, and its function must be marked IMMUTABLE, STRICT, and LEAKPROOF. These restrictions are important because a batch may evaluate up to 63 later rows before the parent asks for them. Such functions must have no side effects, and their catalog declarations must be correct. Other clauses use the existing row-at-a-time path. The patches do not reorder quals. They batch only the supported initial prefix in the order chosen by the planner. The first unsupported qual and everything after it remain row-at-a-time. This also retains the planner's potentially useful ordering for tuple deformation and short-circuit evaluation. Projection still uses the normal ExecProject() path, and it runs only for rows that pass all quals. Thus projection gets no batching benefit in this version. EPQ and cases without a suitable heap page batch also fall back to the existing scalar behavior. The patches are: 0001 Avoid repeated heapgettup_pagemode() calls within a heap page 0002 Evaluate SeqScan quals in heap page batches I also reran the benchmark against the same current master and against Amit's v8 applied to that master. The base was master at 1c4b1de8885; v8 applied cleanly. pass-all is a > 0, and pass-none is a < 0. Each cell below is the change from unpatched master for 5M / 10M rows; negative values mean faster. all-visible: query this series Amit v8 count(*) -6.5% / -7.0% -9.0% / -10.4% count(*) WHERE pass-all -17.6% / -17.7% -5.3% / -4.3% count(*) WHERE pass-none -21.6% / -22.0% -8.7% / -9.4% not-all-visible: query this series Amit v8 count(*) -5.2% / -3.8% -9.2% / -6.8% count(*) WHERE pass-all -17.3% / -16.4% -4.3% / -3.4% count(*) WHERE pass-none -24.8% / -20.4% -12.3% / -7.4% In this run, v8 was somewhat faster for count(*) without a qual. For the two qual cases, local batch evaluation made this series about 13-14% faster than v8. The tables contained a 100-byte text value and about 43 tuples per heap page. They were prewarmed and fit in shared_buffers. Parallel query, JIT, synchronized scans, and autovacuum were disabled. The not-all-visible case used frozen heap tuples with the visibility map truncated. I used an -O2 build on an Apple M5 Pro. For each query I ran three warmups followed by 31 measured EXPLAIN ANALYZE executions and used the median. I repeated each variant twice in symmetric order and averaged the two medians to reduce frequency drift. The full regression suite passes for the two-patch series. I would appreciate feedback on both the heap-local batch representation and the idea of keeping batch qual evaluation inside SeqScan while preserving the ordinary slot interface above it. Best regards, Denis Smirnov
v2-0001-Avoid-repeated-heapgettup_pagemode-calls-within-a.patch
Description: Binary data
v2-0002-Evaluate-SeqScan-quals-in-heap-page-batches.patch
Description: Binary data
