Skip to content

Improve performance of CachedArrayreader filter+concat #10774

Description

@alamb

Is your feature request related to a problem or challenge?

While working on apache/datafusion#24426 (comment)

I am profiling ClickBench Q25 (the one query that is going slower for this setup)

SET datafusion.execution.parquet.binary_as_string = true;
CREATE EXTERNAL TABLE hits_raw STORED AS PARQUET LOCATION '/Users/andrewlamb/Software/datafusion/benchmarks/data/hits_partitioned/';
CREATE VIEW hits AS SELECT * EXCEPT ("EventDate"), CAST(CAST("EventDate" AS INTEGER) AS DATE) AS "EventDate" FROM hits_raw;
SET datafusion.execution.parquet.pushdown_filters = true;
SELECT "SearchPhrase" FROM hits WHERE "SearchPhrase" <> '' ORDER BY "SearchPhrase" LIMIT 10;

About 5% of the overall time is going to reassembling the correct rows in the cached array reader

Image

Describe the solution you'd like

Make it faster

Describe alternatives you've considered

It looks like the CachedArrayreader is currently using the classic "filter + concat" operation

let filtered = arrow_select::filter::filter(&cached, &mask_array)?;
selected_arrays.push(filtered);

1 => Ok(selected_arrays.into_iter().next().unwrap()),
_ => Ok(arrow_select::concat::concat(

Which is what the coalesce kernel is designed to improve (avoids a copy)(: https://docs.rs/arrow/latest/arrow/compute/kernels/coalesce/index.html

So I think we could switch the CachedArrayReader to use coalesce to improve the performance

Additional context

No response

Activity

  1. added theissue type on Aug 21, 2026
  2. changed the title [-]Improve performance of[/-] [+]Improve performance of CachedArrayreader filter+concat[/+] on Aug 21, 2026
  3. zhuqi-lucas commented on Aug 25, 2026

    @zhuqi-lucas
    Contributor

    cc @alamb — I can help with this if you haven't started, thanks!

  4. alamb commented on Aug 25, 2026

    @alamb
    ContributorAuthor

    Thanks @zhuqi-lucas -- I have not started with it yet (I figured I would save my bandwidth to review it)

  5. zhuqi-lucas commented on Aug 28, 2026

    @zhuqi-lucas
    Contributor

    Tried this and the coalesce approach doesn't help here: its single-pass path only fires for all-inline views. When views reference data buffers, copy_rows_by_filter_from does the same filter-then-remap two passes as today's code (the comment there even says "match the filter kernel").

    Measured where the time actually goes in consume_batch (clickbench bench, Q12): filter ~6%, concat ~50% — and the concat cost is the per-view buffer_index remap, not string data (str_len=20 and 128 concat at the same speed; all-inline is 2.7x faster). I implemented three single-pass alternatives (interleave, index-based gather, run-based gather with the remap fused in) — all measurably slower: at ~80% selectivity nothing beats filter's run-based bulk copy.

    What did show up is a quadratic: cleanup_consumed_batches rescans batch ids from 0 and takes the cache write lock on every consume_batch. Fix in #10901 , but from the benchmark result, it seems not good performance improvement.

    The real opportunity the numbers point at: the single-slice path (filter only) shares the source buffers and does zero remapping — 0.058 ns/row vs 0.498 for the multi-slice path, which is only 12.8% of calls but covers 50% of all rows. Rather than a fused kernel, aligning the cache batch size/boundaries with consume_batch's span (row_count reaches ~22k vs batch_size=8192, hence the 7-slice concats) would make the zero-remap path the common case. Worth pursuing?

  6. alamb commented on Sep 1, 2026

    @alamb
    ContributorAuthor

    the single-slice path (filter only) shares the source buffers and does zero remapping — 0.058 ns/row vs 0.498 for the multi-slice path, which is only 12.8% of calls but covers 50% of all rows. Rather than a fused kernel, aligning the cache batch size/boundaries with consume_batch's span (row_count reaches ~22k vs batch_size=8192, hence the 7-slice concats) would make the zero-remap path the common case. Worth pursuing?

    I don't fully understand this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions