ListLayout baseline#8706
Conversation
Signed-off-by: "Matthew Katz" <katz@spiraldb.com> Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matthew Katz" <katz@spiraldb.com> Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matthew Katz" <katz@spiraldb.com> Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: "Matt Katz" <mhkatz97@gmail.com> Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
…ST_LAYOUT Rework ListLayoutStrategy into a streaming transpose driven by the TableStrategy dispatcher (global u64 offsets, memory-bounded), add bounded element reads and is_null pruning routing in ListReader, and gate the whole thing off by default behind the VORTEX_EXPERIMENTAL_LIST_LAYOUT env var / with_list_layout(). Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Merging this PR will not alter performance
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Polar Signals Profiling ResultsLatest Run
Powered by Polar Signals Cloud |
Benchmarks: PolarSignals ProfilingVortex (geomean): 1.034x ➖ How to read Verdict and Engines
datafusion / vortex-file-compressed (1.034x ➖, 0↑ 0↓)
File Size Changes (1 files changed, -0.0% overall, 0↑ 1↓)
Totals:
|
Benchmarks: TPC-H SF=1 on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.993x ➖, 0↑ 0↓)
datafusion / parquet (1.002x ➖, 0↑ 0↓)
datafusion / arrow (1.012x ➖, 0↑ 1↓)
duckdb / vortex-file-compressed (1.002x ➖, 0↑ 0↓)
duckdb / parquet (0.989x ➖, 3↑ 1↓)
File Size Changes (17 files changed, -44.5% overall, 4↑ 13↓)
Totals:
|
Benchmarks: FineWeb NVMeVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.024x ➖, 0↑ 0↓)
datafusion / parquet (0.975x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (1.010x ➖, 0↑ 0↓)
duckdb / parquet (0.982x ➖, 0↑ 0↓)
File Size Changes (3 files changed, -46.3% overall, 0↑ 3↓)
Totals:
|
Benchmarks: TPC-DS SF=1 on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.019x ➖, 0↑ 4↓)
datafusion / parquet (1.028x ➖, 0↑ 4↓)
duckdb / vortex-file-compressed (1.013x ➖, 2↑ 2↓)
duckdb / parquet (1.004x ➖, 0↑ 1↓)
File Size Changes (30 files changed, -43.5% overall, 2↑ 28↓)
Totals:
|
Benchmarks: Clickbench Sorted on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.965x ➖, 1↑ 0↓)
datafusion / parquet (1.007x ➖, 1↑ 0↓)
duckdb / vortex-file-compressed (0.949x ➖, 2↑ 0↓)
duckdb / parquet (0.988x ➖, 0↑ 0↓)
File Size Changes (201 files changed, -42.6% overall, 53↑ 148↓)
Totals:
|
Benchmarks: FineWeb S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.084x ➖, 0↑ 1↓)
datafusion / parquet (1.055x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (1.071x ➖, 0↑ 1↓)
duckdb / parquet (1.029x ➖, 0↑ 0↓)
|
Benchmarks: Statistical and Population GeneticsVerdict: No clear signal (low confidence) How to read Verdict and Engines
duckdb / vortex-file-compressed (1.047x ➖, 0↑ 0↓)
duckdb / parquet (1.038x ➖, 0↑ 0↓)
File Size Changes (3 files changed, -32.3% overall, 0↑ 3↓)
Totals:
|
Benchmarks: Clickbench on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.002x ➖, 0↑ 0↓)
datafusion / parquet (0.992x ➖, 2↑ 0↓)
duckdb / vortex-file-compressed (0.989x ➖, 5↑ 1↓)
duckdb / parquet (0.997x ➖, 1↑ 0↓)
File Size Changes (201 files changed, -39.1% overall, 55↑ 146↓)
Totals:
|
Benchmarks: TPC-H SF=10 on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.001x ➖, 0↑ 0↓)
datafusion / parquet (0.998x ➖, 0↑ 0↓)
datafusion / arrow (1.022x ➖, 0↑ 1↓)
duckdb / vortex-file-compressed (0.980x ➖, 0↑ 0↓)
duckdb / parquet (0.997x ➖, 0↑ 1↓)
File Size Changes (47 files changed, -44.4% overall, 10↑ 37↓)
Totals:
|
Benchmarks: TPC-H SF=1 on S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.082x ➖, 0↑ 2↓)
datafusion / parquet (1.132x ➖, 0↑ 5↓)
duckdb / vortex-file-compressed (1.096x ➖, 0↑ 0↓)
duckdb / parquet (1.108x ➖, 0↑ 1↓)
|
- Extract transpose_list_column with named senders in the writer - Fold use_experimental_list_layout into the builder default - Reader: fetch_raw_offsets/elements helpers, clearer names, doc fixes Signed-off-by: Matt Katz <mhkatz97@gmail.com>
List Layout
Decomposes a
list<T>column into three independently-written sub-columns —elements,offsets, and (when nullable)validity— each backed by its own layout, under a singlevortex.listnode.There are three pieces: the dispatcher enables using
ListLayoutfor arrays withDType::List, the writer shreds a list stream into the three child streams, and the reader reassembles only the children an expression actually needs.Note that this a prototype and is not a performance improvement over flattening lists... yet. As such, this PR does not yet enable writing
ListLayoutfor benchmarks.1.
Recursive Writer DispatchTableStrategyinspects the dtype of the stream it's handed and routes:StructStrategyListLayoutStrategy(when list decomposition is on)To enable recursive handling of nested lists (elements are lists) or structs it hands a descended copy of itself as
the child strategy. So a
list<struct<{ a, list<i32> }>>recurses with no manual wiring — eachlevel dispatches on its own dtype.
List decomposition is gated (
use_experimental_list_layout()/VORTEX_EXPERIMENTAL_LIST_LAYOUT,or an explicit builder call). When off, lists fall through to the leaf strategy unchanged.
2.
ListLayoutStrategyA structural writer: it does not compress or inspect element dtypes; it shreds a list stream into
its three sub-streams and hands each to its own downstream strategy, producing one
ListLayout.To do so it must:
Listparts. A gapped/reorderedListViewis rebuiltinto a gapless
Listfirst (list_from_list_view).u64. Each chunk's local offsets are shifted byelement_base(the running count of elements emitted so far) and widened to
u64, and the duplicated boundaryoffset is dropped on every chunk after the first — so the concatenation of all chunks is one
monotonic
[0 … total_elements]array of lengthrow_count + 1that indexes into the singleconcatenated
elementschild.elements,offsets, andvalidityonto three bounded (capacity-1) channels; eachchild is written concurrently by its own strategy via
spawn_nested. The producer future isjoined with the child writers (
try_join) so a producer error surfaces instead of being hiddenas an early channel close.
Non-list input is forwarded to a
fallbackstrategy unchanged.Because each child is written through an independent strategy, the children's physical shape is
decoupled: routing
elementsthrough the recursive dispatcher (→ repartition → chunked) makes it aChunkedLayoutchunked in element space, whileoffsets/validitycan be written with a different(e.g. row-space) strategy.
3.
ListReaderTo read as little as possible,
projection_evaluationfirst classifies theexpression (
get_necessary_list_children) into the minimal set of children it needs, then routesto a matching read path:
ListChildrenNeeded)Validityis_null(x)/is_not_null(x)project_validityOffsetsAndValiditylist_length(x)project_offsets_validityAllx, or anything over elementsproject_allproject_allmaterializes the list, and itself picks between two sub-paths:project_all_concurrent) — used for a full-column range, or whenelementsis a single flat segment (nothing to skip). Fires theelements,offsets, andvalidityreads concurrently, assembles the list, slices to the requested range, and filters.No offsets→elements ordering dependency.
project_all_elements_bounded) — used for a strict sub-range over chunkedelements. Reads
offsets[range], decodes the first/last offset to bound the elements read to[first … last), fetches only the element chunks that range overlaps, and rebases the offsetsto index into the sliced buffer. Costs one offsets→elements round-trip but avoids reading the
whole elements column for a partial scan split.
TODOs:
Status / open decisions
ListLayoutinstead of just chunking children? Doing aligns the row range covered by element chunks and potentially offset chunks within a list chunk.