Avatar for the vortex-data user
vortex-data
vortex
BlogDocsChangelog

Performance History

Latest Results

fix(arrow): return an error for incompatible Arrow export targets `execute_arrow_naive` dispatches purely on the requested Arrow `DataType`, but the bool, null, decimal, list, list-view, and fixed-size-list arms then call `execute::<T>`, which is documented as panicking when the array's dtype does not match. Exporting an array to an incompatible Arrow type therefore panicked instead of returning an error. Guard each arm with `vortex_ensure!` on the source dtype, mirroring the existing check in `to_arrow_byte_array`. The guards are per-arm rather than centralised because compatibility is not symmetric: a Bool source exports to Int32 via a cast kernel, while a Primitive source cannot produce Boolean, so a single "type classes must match" rule would reject conversions that work. Signed-off-by: Robert Kruszewski <robert@spiraldb.com>
claude/arrow-target-dtype-check
8 hours ago
Allow skipping scan expression optimization
codex/skip-scan-expression-optimize
10 hours ago
Allow skipping scan expression optimization
codex/skip-scan-expression-optimize
10 hours ago
perf(array): bulk-append paths for DynVarBinBuilder `DynVarBinBuilder::append_zeros`, `append_n_values`, and `append_varbinview` each looped over a per-element append that re-read the `DynOffsets` enum for every row. Resolve the offset width once per call and append through a monomorphized `VarBinBuilder<O>`. Two of the loops also did avoidable per-element work: - `append_zeros` pushed `n` empty values one at a time. Add `VarBinBuilder::append_n_empty`, which repeats the previous offset with `push_n` and appends validity with `append_n`. - `append_varbinview` called `VarBinViewData::bytes_at` per row, which re-derives the views slice and returns a refcounted `ByteBuffer`. Read the views slice once and borrow each view's payload instead. The enum hoist alone measured neutral; the wins come from the bulk offset writes and from dropping the per-row `ByteBuffer`. Medians from a local divan benchmark over the three append paths (not included in this change): | bench | before | after | | --- | --- | --- | | `append_zeros/1024` | 4.073 µs | 139.7 ns | | `append_zeros/65536` | 264.1 µs | 8.700 µs | | `append_n_values/1024` | 7.349 µs | 5.647 µs | | `append_n_values/65536` | 1.222 ms | 408.3 µs | | `append_varbinview/no_nulls/1024` | 58.71 µs | 9.623 µs | | `append_varbinview/no_nulls/65536` | 4.952 ms | 1.522 ms | | `append_varbinview/nulls/1024` | 48.15 µs | 9.300 µs | | `append_varbinview/nulls/65536` | 3.538 ms | 1.339 ms | Signed-off-by: "Robert" <robert@spiraldb.com>
claude/dynvarbinbuilder-review-0zqkhq
11 hours ago
more Signed-off-by: Robert Kruszewski <github@robertk.io>
claude/opendal-integration-review-1h8f3a
12 hours ago
Add a branch-free splat form for word-scale runs The splat tests, per run, whether the run reaches the end of the current word. That test is data-dependent, and its predictability tracks the run length: for runs averaging r bits it fires about r/64 of the time. Well below a word it almost never fires and predicts perfectly. Around half a word it is a coin flip, and the mispredictions dominate the loop -- which is why the per-run cost peaked at run length 32 rather than falling monotonically. Add a form with no such test. It paints the run from the current bit offset all the way to the top of the word and stores unconditionally: word = (word & low_mask(bits)) | (splat << bits); words[idx] = word; let completed = (bits + n) / 64; ... idx += completed; word = if completed > 0 { splat } else { word }; bits = (bits + n) % 64; Bits above `bits` are don't-care, so painting past the run's end is free: the next run blends over them. Two conditions make it correct. The output needs one word of slack, because a zero-length run arriving once the buffer is exactly full still stores -- and run ends clamped to the logical length produce exactly that. And `finish` must mask the last partial word, or the paint survives past the logical length where `true_count` would read it. `blend_form_matches_naive` covers both, at three lengths chosen to land on and off the word grid. It is not strictly better, so both forms stay, picked by average run length, which is O(1) from the run count. Blend over branching, by fastest sample: run length 1 2 8 16 32 48 64 128 1024 non-nullable 0.75 0.79 1.02 1.35 1.71 1.39 1.15 0.86 0.74 nullable 0.82 0.88 1.15 1.37 1.71 1.79 1.74 1.89 1.16 Below a quarter of a word the redundant stores -- up to 64 per word of output -- cost more than the branch. The nullable kernel crosses over earlier: it does about twice the work per run, so the extra stores are proportionally cheaper while a misprediction costs the same. The blend path is `#[inline(never)]`. This is not a micro-preference: with the two loops inlined into one function, the branching loop lost 20% with its own text unchanged, measured against an in-process anchor that did not move. Expressing the choice as a const generic over one loop did the same. Only keeping the second loop out of line leaves the first one alone. On the corpus benchmark, against the previous commit: run16 cases 1.16-1.37x, run64 cases up to 1.50x, run4 cases unchanged. Signed-off-by: "Claude" <noreply@anthropic.com>
claude/runend-decompress-bools-1t2re2
12 hours ago

Latest Branches

CodSpeed Performance Gauge
0%
fix(arrow): return an error for incompatible Arrow export targets#8993
8 hours ago
7c8bdfe
claude/arrow-target-dtype-check
CodSpeed Performance Gauge
0%
10 hours ago
7cd0650
codex/skip-scan-expression-optimize
CodSpeed Performance Gauge
+10%
12 hours ago
5392bac
claude/dynvarbinbuilder-review-0zqkhq
Ā© 2026 CodSpeed Technology
Home Terms Privacy Docs