You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf(sort-merge): cache current-row bytes in RowValues for SortPreservingMerge
Multi-column sorts serialize the sort key into arrow `Rows` and wrap it in
`RowValues`. Every loser-tree comparison called `Rows::row(idx)` for each
side, and each call walks `Arc<Rows>` -> offsets[idx] / offsets[idx+1] ->
buffer slice. The offsets buffer is far too large to stay cache-resident,
so those lookups typically cost an L2/L3 hit with a DRAM tail — repeated
for the same idx on every compare of a stable loser-tree head.
Cache the current row's `(ptr, len)` once per `Cursor::advance` (via the
existing `CursorValues::set_offset` hook) and read it in `compare`, so the
hot path resolves to two plain field loads plus a memcmp. The pointer is
into the Arc-owned buffer heap and stays valid across struct moves (the
cursor is written into a `Vec<Option<Cursor<..>>>` slot), so `Send`/`Sync`
are implemented by hand with a SAFETY note. `eq` / `eq_to_previous` take
arbitrary cross-batch indices and continue to index `Rows` directly.
This is the first of a series splitting apart the SPM cursor-cache work
for easier review; it carries the largest share of the win (multi-column
sort-tpch). Follow-ups add the same pattern to the single-column string
cursors and a null-wrapper fast path.
sort_tpch10 benchmark on the full series showed the multi-column queries
(Q4 +1.23x, Q8 +1.13x, Q9 +1.15x, Q5/Q6/Q11 ~+1.12x) driven by this cache.
Tests: `test_row_values_cache_matches_rows_index` checks the cached
ordering against per-row `Rows` indexing across every offset of a batch,
including the cross-batch `eq` path; `test_row_values_single_row_batch`
covers the up-front row-0 cache. Existing `sorts::*` merge tests (83)
pass.
0 commit comments