Skip to content

Faster reads: unchecked AnyList, unchecked null probe, inline accessors#17

Merged
emilk merged 1 commit into
mainfrom
faster-reads-unchecked
Jun 12, 2026
Merged

Faster reads: unchecked AnyList, unchecked null probe, inline accessors#17
emilk merged 1 commit into
mainfrom
faster-reads-unchecked

Conversation

@emilk

@emilk emilk commented Jun 12, 2026

Copy link
Copy Markdown
Member

Three hot-path follow-ups to #15 (value_unchecked iteration). Reading a validated column is the hot path — bounds are known once, so per-element re-checks are wasted work.

Changes

1. AnyList unchecked reads (any_list.rs)
AnyList only implemented value, so it inherited the trait's default value_unchecked (which forwards to the bounds-checked value). Every element of a Column<AnyList<_>> therefore re-checked bounds — the exact cost #15 removed elsewhere, but missed for the any-encoding list type. Added value_unchecked + is_null_unchecked dispatching to each concrete encoding.

2. Unchecked null probe (datatype.rs, option.rs, leaves)
New LogicalType::is_null_unchecked (safe default = is_null). Option<L>::value_unchecked read the value unchecked but still probed nullity through the bounds-checked is_null, so nullable iteration kept paying a per-element check on the validity bitmap. One helper leaf_is_null_unchecked reads the bitmap via nulls().inner().value_unchecked(); leaf encodings override it, trivial wrappers (Option, newtypes, As, duration, timestamp, Run, AnyList) forward it. Dictionary keeps its value lookup checked — a malformed key must not be UB.

3. #[inline] sweep
The crate had zero #[inline]. Added it to the per-element accessors (is_null, is_null_unchecked, value, value_unchecked, value_ref, values), TypedArray accessors, and the Column/ListValue iterator next — tiny generic forwarders crossing the quiver_typesquiver crate boundary, where inlining the wrapper chains is not guaranteed.

Safety / testing

  • is_null_unchecked is unsafe (precondition index < length), covered by the existing crate-level unsafe_code rationale (updated in lib.rs).
  • Soundness tests extended over sliced arrays for the new paths: nullable_string_column_sliced, any_list_column_sliced, nullable_any_list_column_sliced.
  • cargo clippy --all-features --all-targets clean, full test suite green, Miri clean (no UB).

🤖 Generated with Claude Code

Three hot-path follow-ups to PR #15 (`value_unchecked` iteration):

1. `AnyList` gained `value_unchecked` / `is_null_unchecked`. It previously
   inherited the trait default (which forwards to the bounds-checked `value`),
   so every element of a `Column<AnyList<_>>` re-checked bounds despite the
   range being validated once at construction — exactly the cost #15 set out
   to remove, but missed for the any-encoding list type.

2. New `LogicalType::is_null_unchecked` (safe default = `is_null`). The
   `Option<L>` hot path read the value unchecked but still probed nullity
   through the bounds-checked `is_null`, so nullable iteration kept paying a
   per-element check on the validity bitmap. A single `leaf_is_null_unchecked`
   helper reads the bitmap via `nulls().inner().value_unchecked()`; the leaf
   encodings override it and the trivial wrappers (`Option`, newtypes, `As`,
   duration, timestamp, `Run`, `AnyList`) forward it. `Dictionary` keeps its
   value lookup checked (a malformed key must not be UB).

3. Added `#[inline]` to the per-element accessors (`is_null`,
   `is_null_unchecked`, `value`, `value_unchecked`, `value_ref`, `values`),
   the `TypedArray` accessors, and the `Column`/`ListValue` iterator `next` —
   tiny generic forwarders that cross the `quiver_types` -> `quiver` crate
   boundary, where cross-crate inlining of the wrapper chains is not
   guaranteed. The crate had no `#[inline]` at all before.

Soundness tests extended over sliced arrays for the new paths
(`nullable_string`, `any_list`, `nullable_any_list`); verified with Miri (no UB).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@emilk emilk merged commit 3bcf460 into main Jun 12, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant