[DO NOT MERGE] Experimental RowFn - #9255
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
Polar Signals Profiling ResultsLatest Run
Powered by Polar Signals Cloud |
Benchmarks: Clickbench on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (0.994x ➖, 1↑ 0↓)
datafusion / parquet / ns (0.992x ➖, 1↑ 0↓)
duckdb / vortex-compact / ns (0.977x ➖, 7↑ 3↓)
duckdb / parquet / ns (1.009x ➖, 0↑ 0↓)
File Size Changes (101 files changed, -60.8% overall, 0↑ 101↓)
Totals:
|
Benchmarks: FineWeb S3 📖Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (1.256x ➖, 0↑ 4↓)
datafusion / parquet / ns (1.141x ➖, 0↑ 2↓)
duckdb / vortex-compact / ns (1.015x ➖, 0↑ 0↓)
duckdb / parquet / ns (0.999x ➖, 0↑ 0↓)
|
Benchmarks: Clickbench Sorted on NVME 📖Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (1.003x ➖, 0↑ 0↓)
datafusion / parquet / ns (1.024x ➖, 1↑ 1↓)
duckdb / vortex-compact / ns (1.037x ➖, 1↑ 3↓)
duckdb / parquet / ns (1.009x ➖, 0↑ 0↓)
File Size Changes (201 files changed, -57.2% overall, 53↑ 148↓)
Totals:
|
Benchmarks: TPC-H SF=1 on S3 📖Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (1.060x ➖, 0↑ 1↓)
datafusion / parquet / ns (0.940x ➖, 1↑ 0↓)
duckdb / vortex-compact / ns (1.118x ➖, 0↑ 1↓)
duckdb / parquet / ns (1.044x ➖, 0↑ 0↓)
|
Benchmarks: Appian on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (1.005x ➖, 0↑ 0↓)
datafusion / parquet / ns (0.993x ➖, 0↑ 0↓)
duckdb / vortex-compact / ns (1.007x ➖, 0↑ 0↓)
duckdb / parquet / ns (1.001x ➖, 0↑ 0↓)
File Size Changes (10 files changed, -63.8% overall, 0↑ 10↓)
Totals:
|
Benchmarks: FineWeb NVMe 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (0.994x ➖, 0↑ 0↓)
datafusion / parquet / ns (1.001x ➖, 0↑ 0↓)
duckdb / vortex-compact / ns (0.993x ➖, 0↑ 0↓)
duckdb / parquet / ns (1.006x ➖, 0↑ 0↓)
File Size Changes (2 files changed, -53.7% overall, 0↑ 2↓)
Totals:
|
Benchmarks: TPC-H SF=1 on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (1.000x ➖, 0↑ 0↓)
datafusion / parquet / ns (0.992x ➖, 2↑ 2↓)
duckdb / vortex-compact / ns (0.983x ➖, 1↑ 0↓)
duckdb / parquet / ns (1.000x ➖, 0↑ 0↓)
File Size Changes (9 files changed, -56.1% overall, 0↑ 9↓)
Totals:
|
Benchmarks: TPC-DS SF=1 on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (1.004x ➖, 0↑ 2↓)
datafusion / parquet / ns (1.002x ➖, 1↑ 1↓)
duckdb / vortex-file-compressed / ns (1.014x ➖, 6↑ 10↓)
duckdb / parquet / ns (1.005x ➖, 2↑ 8↓)
File Size Changes (25 files changed, -43.5% overall, 0↑ 25↓)
Totals:
|
Benchmarks: TPC-DS SF=1 on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (1.006x ➖, 1↑ 1↓)
datafusion / parquet / ns (1.000x ➖, 1↑ 2↓)
duckdb / vortex-compact / ns (1.007x ➖, 0↑ 6↓)
duckdb / parquet / ns (1.010x ➖, 4↑ 8↓)
File Size Changes (25 files changed, -56.5% overall, 0↑ 25↓)
Totals:
|
Benchmarks: FineWeb S3 📖Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (1.077x ➖, 0↑ 0↓)
datafusion / parquet / ns (1.103x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed / ns (1.113x ➖, 0↑ 1↓)
duckdb / parquet / ns (0.776x ➖, 1↑ 0↓)
|
Benchmarks: PolarSignals Profiling 📖Vortex (geomean): 1.009x ➖ datafusion / vortex-file-compressed / ns (1.009x ➖, 0↑ 1↓)
No file size changes detected. |
Benchmarks: TPC-H SF=1 on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (1.011x ➖, 0↑ 0↓)
datafusion / parquet / ns (0.994x ➖, 1↑ 0↓)
duckdb / vortex-file-compressed / ns (1.007x ➖, 0↑ 1↓)
duckdb / parquet / ns (1.003x ➖, 0↑ 0↓)
File Size Changes (9 files changed, -43.9% overall, 0↑ 9↓)
Totals:
|
Benchmarks: TPC-H SF=1 on S3 📖Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (0.990x ➖, 1↑ 0↓)
datafusion / parquet / ns (0.965x ➖, 1↑ 0↓)
duckdb / vortex-file-compressed / ns (1.018x ➖, 0↑ 1↓)
duckdb / parquet / ns (1.029x ➖, 0↑ 0↓)
|
Benchmarks: FineWeb NVMe 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (0.996x ➖, 1↑ 0↓)
datafusion / parquet / ns (0.995x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed / ns (1.015x ➖, 2↑ 1↓)
duckdb / parquet / ns (1.008x ➖, 0↑ 0↓)
File Size Changes (2 files changed, -46.3% overall, 0↑ 2↓)
Totals:
|
Benchmarks: Statistical and Population Genetics 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
duckdb / vortex-file-compressed / ns (1.026x ➖, 2↑ 4↓)
duckdb / parquet / ns (1.007x ➖, 0↑ 0↓)
File Size Changes (2 files changed, -32.3% overall, 0↑ 2↓)
Totals:
|
Benchmarks: TPC-H SF=10 on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (0.984x ➖, 0↑ 0↓)
datafusion / parquet / ns (1.001x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed / ns (0.990x ➖, 0↑ 0↓)
duckdb / parquet / ns (1.001x ➖, 0↑ 1↓)
File Size Changes (9 files changed, -44.0% overall, 0↑ 9↓)
Totals:
|
Benchmarks: Clickbench on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (0.999x ➖, 1↑ 2↓)
datafusion / parquet / ns (1.008x ➖, 1↑ 1↓)
duckdb / vortex-file-compressed / ns (1.021x ➖, 1↑ 7↓)
duckdb / parquet / ns (0.997x ➖, 0↑ 1↓)
File Size Changes (101 files changed, -39.2% overall, 0↑ 101↓)
Totals:
|
Benchmarks: Statistical and Population Genetics 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
duckdb / vortex-compact / ns (1.040x ➖, 1↑ 3↓)
duckdb / parquet / ns (0.994x ➖, 0↑ 0↓)
File Size Changes (2 files changed, -67.7% overall, 0↑ 2↓)
Totals:
|
Benchmarks: TPC-H SF=10 on S3 📖Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (0.930x ➖, 1↑ 0↓)
datafusion / parquet / ns (0.894x ➖, 3↑ 1↓)
duckdb / vortex-compact / ns (1.008x ➖, 0↑ 0↓)
duckdb / parquet / ns (0.917x ➖, 0↑ 0↓)
|
Benchmarks: Clickbench Sorted on NVME 📖Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (1.002x ➖, 1↑ 1↓)
datafusion / parquet / ns (1.025x ➖, 0↑ 2↓)
duckdb / vortex-file-compressed / ns (1.025x ➖, 0↑ 1↓)
duckdb / parquet / ns (0.990x ➖, 0↑ 0↓)
File Size Changes (201 files changed, -42.8% overall, 50↑ 151↓)
Totals:
|
Benchmarks: TPC-H SF=10 on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-compact / ns (1.007x ➖, 0↑ 0↓)
datafusion / parquet / ns (0.989x ➖, 0↑ 0↓)
duckdb / vortex-compact / ns (1.007x ➖, 0↑ 0↓)
duckdb / parquet / ns (0.993x ➖, 1↑ 0↓)
File Size Changes (9 files changed, -56.0% overall, 0↑ 9↓)
Totals:
|
Progress towards #9128. `RowFn` derives the whole of `ScalarFnVTable` from a row closure: name the element types, write the closure, and lifting supplies null propagation, constant folding, nullability, validity, and options serde. The input side is open. `InputElement::Elem` is a GAT, so an element can hand the closure borrowed variable-length data or drill through a wrapper to an extension array's storage. Covering a new type family is one impl. The output is always an `OutputSink`, allocated once per batch and handing the closure one row to write. `ElementSink` covers one owned `OutputElement` per row, and a custom sink carries runtime-shaped output such as a tensor whose width comes from its input dtype. Work that depends only on a batch-constant operand goes in `RowVisitor::visit_prepared_into`'s once-per-batch prepare step. Prepare must not be load-bearing for validation, because an empty batch decodes every operand as non-constant. Null-strategy selection is derived too. A nullable batch runs densely, by branch-and-skip, or by filtering, and the framework picks per batch. The one input an element controls is `InputElement::FILTERED_DECODE_COST`, set when decoding a column does expensive per-row work, so sparse batches keep the filter strategy's shrunken decode. Two things send a function to `ScalarFnVTable` instead, and no output sink covers either: a result that aliases an input, and a null result for a non-null row. The module docs on `scalar_fn` record the full choice between the two traits. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-authored-by: Claude <noreply@anthropic.com>
Progress towards #9128. `row_fn_executor` measures the derived execution loop against a hand-written columnar kernel of the same arithmetic, so the cost of going through `RowFn` is separated from the cost of the operation. `strict_validity` measures the three null strategies against each other across validity densities, which is the evidence behind the per-batch selection rule. `like` gains `like_per_row_distinct_patterns`, which gives every row a distinct pattern of the same shape so the compile cache never hits. Paired with `like_per_row_patterns` it separates the cost of compiling a pattern from the cost of matching against it, which is what a kernel that cannot cache across rows pays. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-authored-by: Claude <noreply@anthropic.com>
Progress towards #9128. `vortex.numeric` becomes a `RowFn` over a primitive pair, taking `NumericOperator` as its options, which deletes the hand-written null propagation, constant folding, and validity logic in `numeric/primitive.rs`. Checked arithmetic reports overflow as evidence the row closure returns, rather than as a comparison the caller re-derives. Decimal keeps its own columnar implementation. `PrimitiveOperand` was defined in `numeric/primitive.rs` and shared out of `numeric/mod.rs`. The port drops its numeric caller, so it moves into `compare/primitive.rs`, its only remaining user. `NumericOperator` gains `Hash`, which a `RowFn`'s options require. `map_checked_into` in `vortex-compute` loses its last caller with the port and is deleted. Also adds a `list_length` test pinning that a non-nullable fixed-size list keeps a constant result rather than materializing one `u64` per row, which is the reason `vortex.list.length` stays on `ScalarFnVTable`. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-authored-by: Claude <noreply@anthropic.com>
Progress towards #9128. `vortex.tensor.l2_norm`, `vortex.tensor.inner_product`, and `vortex.tensor.cosine_similarity` become row functions over a `TensorRow` element, which yields a slice of the extension array's storage straight to the closure. Lifting supplies the null propagation, constant folding, nullability, validity, and options serde that the three kernels each wrote by hand. Cosine similarity hoists the norm of a broadcast query vector into `visit_prepared_into`'s prepare step. The prepared and per-row arms must agree bit for bit, which only holds while both accumulate in the same order, so `l2_norm_row` moves into `utils.rs` and both call it. `BinaryTensorOpMetadata` and `build_tensor_array` move there too, shared by the two binary operators and by the normalized encoding. Constant folding through `try_build_constant_normalized` is now derived from the row closure, so the export is gone. The tests move out of the three kernel modules and into `scalar_fns/tests/`. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-authored-by: Claude <noreply@anthropic.com>
Progress towards #9128. `vortex.geo.distance`, `vortex.geo.contains`, and `vortex.geo.intersects` become row functions, which deletes `scalar_fn/execute.rs` and the shared columnar execution it held. Decoding a geometry does expensive per-row work, so the geo elements set `InputElement::FILTERED_DECODE_COST` and sparse batches keep the filter strategy's shrunken decode. Branch-and-skip needs a decode that tolerates null rows without parsing them, so `geometries_null_tolerant` writes a placeholder geometry into null slots for `Point` and `Polygon`. It returns `Ok(None)` for any other geometry type, and the caller falls back to the filter strategy, which never decodes a null row. `contains` prepares a constant geometry once per batch rather than re-preparing it per row. The row layer sees through extension-over-constant, so the hand-written rewrite that used to uncover the constant is gone. `geo` is pinned to `=0.31.0` in the workspace manifest. `contains_route` transcribes geo's `impl_contains_from_relate!` dispatch table, so any bump that moves a row silently changes containment verdicts while the tests stay green wherever relate and the direct algorithm agree. A caret requirement would let `cargo update` take 0.31.x with no diff to review. `null_strategies` benchmarks the three strategies against `GeoContains` across validity densities, which is where the crossover between filtering and branch-and-skip was measured. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-authored-by: Claude <noreply@anthropic.com>
Progress towards #9128. `STRICT_SCALAR_FN_RESEARCH.md` holds the null-strategy measurements behind the per-batch selection rule, the three verdicts and the crossover, and the record of why `vortex.byte_length`, `vortex.not`, and `vortex.list.sum` stay on `ScalarFnVTable`. `SCALAR_FN_HANDOFF.md` records the current state of the work, including which API proposals from the review were backed out and why. `NUMERIC_ROWFN_PLAN.md` is the plan and measured outcome for the numeric port. `docs/strictness-and-validity-pushdown.typ` writes up strictness and validity pushdown, which is the property the whole derivation rests on. These are working notes rather than published documentation, and they are separated here so they are easy to drop before this ships. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-authored-by: Claude <noreply@anthropic.com>
Progress towards #9128. `like_per_row_patterns` kept its name but changed its pattern from `hello%` to `%aaa%` so it could pair with `like_per_row_distinct_patterns`. CodSpeed matches benchmarks by name, so it compared a prefix match against a substring search and reported a 24% regression in a function this branch never touched. The pattern goes back to `hello%`, and `%aaa%` moves to `like_per_row_repeated_patterns`. The pair is preserved, both halves of it are new names with no history to invalidate, and `like_per_row_patterns` measures on this branch what it measures on develop. Signed-off-by: Connor Tsui <connor@spiraldb.com> Co-authored-by: Claude <noreply@anthropic.com>
EXPERIMENTAL
(for benchmarking purposes)
See #9128