Skip to content

[C++][Parquet] Using BMI to implement filter pushdown #37559

Open

Description

@baibaichen

Describe the enhancement requested

From Selection Pushdown in Column Stores using Bit Manipulation Instructions, they said

We empirically evaluate the proposed techniques in the context of Apache Parquet using both micro-benchmarks and the TPC-H benchmark, and show that our techniques improve the query performance of Parquet by up to one order of magnitude with representative scan queries. Further experimentation using Apache Spark demonstrates speed improvements of up to 5.5X even for end-to-end queries involving complex joins.

And

We implemented Parquet-Select in C++, based on the open-source C++ version of the Arrow/Parquet library^1^ (version 8.0.0). For all operators that use BMI instructions, we implemented a dynamic dispatching mechanism to detect whether BMI support is available on the host processors at run time and fall back to an alternative implementation if BMI support is missing.

BMI was instroduced 10 years ago, so implementing this feature should have big benefits.

Component(s)

C++, Parquet

Activity

  1. mapleFU commented on Sep 5, 2023

    @mapleFU
    Member

    When it goes to rep-def levels, I believe we already have some BMI2 impls:

    void DefLevelsToBitmapBmi2WithRepeatedParent(const int16_t* def_levels,

    However, other decode part missing this.

    Besides, currently, the Arrow Parquet implemention even don't implement the Predicate pushdown in C++. Acero has some SelectionVector, but it doesn't use that.

    Maybe we can evaluate and implement that step by step, like Velox 🤔?

  2. baibaichen commented on Sep 5, 2023

    @baibaichen
    Author

    Maybe we can evaluate and implement that step by step, like Velox 🤔?

    I am currently working on gluten and clickhouse backend. Our team try to implement a filter push down like velox but using clickhouse expression evaluate framework which is not good since we have to build full Column before filter operation.

    The paper gives right direction, and I think we should implement filter push donw like this in apache arrow.

    Would we have a meeting on this?

  3. mapleFU commented on Sep 5, 2023

    @mapleFU
    Member

    What kind of interface does ClickHouse using around Parquet? I guess there are lots of work todo. first the expression should be cast to arrow::Expression, and then we need support some lazy evaluation / filter reorder by selector or a row-range based on the arrow::Expression.

    Technically, the way like arrow-rs might be followed. As for BMI itself, I guess it's better an optional optimization rather than a neccessary-opt (like decode rep-deps and unpack in current parquet implemention)

  4. baibaichen commented on Sep 5, 2023

    @baibaichen
    Author

    As for BMI itself, I guess it's better an optional optimization rather than a neccessary-opt (like decode rep-deps and unpack in current parquet implemention)

    Agreed!

    What kind of interface does ClickHouse using around Parquet?

    see ArrowBlockInputFormat , clickhouse use RecordBatchFileReader

    I guess there are lots of work todo. first the expression should be cast to arrow::Expression, and then we need support some lazy evaluation / filter reorder by selector or a row-range based on the arrow::Expression. Technically, the way like arrow-rs might be followed.

    We lack knowledge of how to build an expression evaluate framework based on arrow. It would be great that the arrow community could do this.

    By the way, we also find that memmory copy hurt performanace, is it possible to directly decode into Clickhouse's data structure? The current code path looks like this :

    parquet encoded buffer => arrow => clickhouse.
    

    see ArrowColumnToCHColumn

  5. mapleFU commented on Sep 5, 2023

    @mapleFU
    Member

    By the way, we also find that memmory copy hurt performanace, is it possible to directly decode into Clickhouse's data structure?

    Hmmm If ClickHouse support non-owned Buffer, or other, it could be simpler 🤔 Otherwise you need to implement the logic under cpp/src/parquet/arrow... I guess thats really huge work todo(it will be easy at first, but finally you will need to write wrapper for every types, timestamp, decimal..). Though memcpy performance for binary type might be pool now, I guess it would be benefits when StringView and other is introduced.

  6. baibaichen commented on Sep 5, 2023

    @baibaichen
    Author

    . I guess thats really huge work todo(it will be easy at first, but finally you will need to write wrapper for every types, timestamp, decimal..). Though memcpy performance for binary type might be pool now,

    Yes! that's why filter push down is important, which colud reduce memory copy significantly.

  7. mapleFU commented on Sep 6, 2023

    @mapleFU
    Member

    Another thing is that have you do the field pruning and RowGroup statistics pruning? This could be done easily

  8. mapleFU commented on Sep 6, 2023

    @mapleFU
    Member

    Would we have a meeting on this?

    Maybe you can email me and @wgtmac . And when we have some RFC maybe we can deliver it in arrow weekly meeting?

  9. changed the title [-][C++][Parquet]: Using BMI to implement filter pushdonw[/-] [+][C++][Parquet] Using BMI to implement filter pushdonw[/+] on Sep 6, 2023
  10. baibaichen commented on Sep 6, 2023

    @baibaichen
    Author

    Would we have a meeting on this?

    Maybe you can email me and @wgtmac . And when we have some RFC maybe we can deliver it in arrow weekly meeting?

    Cool!
    @mapleFU @wgtmac let me know what's your timezone, so I can schedule a meeting.

  11. changed the title [-][C++][Parquet] Using BMI to implement filter pushdonw[/-] [+][C++][Parquet] Using BMI to implement filter pushdown[/+] on Sep 6, 2023
  12. mapleFU commented on Sep 6, 2023

    @mapleFU
    Member

    We're at UTC-8 now. We can chat these in mail since github issue is public, and it might not directly related to the issue?

  13. 31 remaining items

  14. wgtmac commented on Nov 27, 2023

    @wgtmac
    Member

    Finally I have got some time to complete the design doc drafted by @mapleFU: https://docs.google.com/document/d/1SeVcYudu6uD9rb9zRAnlLGgdauutaNZlAaS0gVzjkgM/.

    The proposal mainly is based on the discussion with @baibaichen @binmahone.

    I think it would be good to get a consensus from the community before getting hands dirty on the implementation. Please take a look and any feedback are welcome! @emkornfield @fatemehp @pitrou @westonpace

  15. pitrou commented on Nov 27, 2023

    @pitrou
    Member

    Finally I have got some time to complete the design doc drafted by @mapleFU: https://docs.google.com/document/d/1SeVcYudu6uD9rb9zRAnlLGgdauutaNZlAaS0gVzjkgM/.

    This proposes a number of reader APIs based on row ranges, but never says how row ranges are computed in the first place?

  16. emkornfield commented on Nov 28, 2023

    @emkornfield
    Contributor

    Finally I have got some time to complete the design doc drafted by @mapleFU: https://docs.google.com/document/d/1SeVcYudu6uD9rb9zRAnlLGgdauutaNZlAaS0gVzjkgM/.

    This proposes a number of reader APIs based on row ranges, but never says how row ranges are computed in the first place?

    @pitrou I think the intent is that it is specifically abstract. I think there are a few different methods to produce the ranges:

    1. RowGroup level selection
    2. Page level selection via indices.
    3. "deletion vectors" from OSS table formats like Delta Lake and iceberg which specify which rows in the file are logically deleted.
    4. Using something like Arrow compute to select specific rows based off a few columns and construct the ranges.

    @wgtmac I think this is partially covered in the design but it would be good to maybe make this more explicit

  17. wgtmac commented on Nov 28, 2023

    @wgtmac
    Member

    Finally I have got some time to complete the design doc drafted by @mapleFU: https://docs.google.com/document/d/1SeVcYudu6uD9rb9zRAnlLGgdauutaNZlAaS0gVzjkgM/.

    This proposes a number of reader APIs based on row ranges, but never says how row ranges are computed in the first place?

    I agree with @emkornfield that there are many approaches to produce row ranges.

    AFAIK, many downstream projects have different expression APIs and only use the arrow layer of parquet-cpp (not the dataset layer). It is difficult to determine a single approach of producing row ranges for all downstream projects, but it is easy to make the agreement to push down row ranges to the parquet reader to achieve filtering. Therefore I leave the freedom of different engines to design their own logic to produce row ranges.

    At the moment, I have marked filtering support of parquet dataset reader as a non-goal in the doc. My idea is to use expressions from Arrow compute to do something similar to what parquet-mr does: https://github.com/apache/parquet-mr/blob/master/parquet-column/src/main/java/org/apache/parquet/internal/filter2/columnindex/ColumnIndexFilter.java#L65-L104.

  18. fatemehp commented on Dec 1, 2023

    @fatemehp
    Contributor

    Why is it necessary to have a RowRanges API? The ::parquet::internal::RecordReader has ReadRecords and SkipRecords APIs, this should be sufficient to read/skip ranges of rows.

    This will reduce the burden of converting whatever upstream format of row ranges to the one compatible with what we define here.

    P.S. We have considered removing the RecordReader from the internal scope:
    #37003 (comment)

  19. wgtmac commented on Dec 1, 2023

    @wgtmac
    Member

    Why is it necessary to have a RowRanges API? The ::parquet::internal::RecordReader has ReadRecords and SkipRecords APIs, this should be sufficient to read/skip ranges of rows.

    This will reduce the burden of converting whatever upstream format of row ranges to the one compatible with what we define here.

    P.S. We have considered removing the RecordReader from the internal scope: #37003 (comment)

    IMO, it would be too late to optimize I/O and decoding when SkipRecords is called. Pushing down row ranges has good separation and makes it easy to do the planning before reading.

  20. fatemehp commented on Dec 1, 2023

    @fatemehp
    Contributor

    I added a comment to the doc, I think we should think more about the format of the ranges both in terms of memory and performance.

    I would consider analyzing the following options before making a decision:

    1. A set of runs of 0s and 1s. These could be storage optimized/encoded and cheaply read.

    2. Storing num_to_read/num_to_skip instead of from/to. If we limit the size of the runs, we could store them in uint16_t instead of the proposed int64_t for from/to.

    3. A storage-optimized bitmap such as a RoaringBitmap.

  21. emkornfield commented on Dec 1, 2023

    @emkornfield
    Contributor

    P.S. We have considered removing the RecordReader from the internal scope: #37003 (comment)

    @fatemehp I think we are all ok with this, it is just waiting a PR: #37003 (comment)

  22. wordhardqi commented on Jan 4, 2024

    @wordhardqi

    do we have a PR for BMI now

  23. mapleFU commented on Jan 4, 2024

    @mapleFU
    Member

    @wordhardqi Not yet, we should first planning a interface for high level pruning, and the work is on-going now

    However, if you're interesting in BMI implemention, implement better DecodeSpaced would help the latter implementation.

  24. pitrou commented on Jan 4, 2024

    @pitrou
    Member

    do we have a PR for BMI now

    Please, stop obsessing over BMI or other "magical" optimization methods (which SIMD instructions are often viewed as). The correct way to reason about this should be 1) find bottlenecks 2) find portable methods to optimize them 3) only as a last resort, consider SIMD optimizations.

  25. felipecrv commented on Mar 2, 2024

    @felipecrv
    Contributor

    There is a lot in the Filter and Take implementations that can be improved even before we have the code structured in a way that can benefit from SIMD speed-ups. I'm marking myself as "Participating" on this discussion so I remember to announce the extraction of primitives that can be useful here.

  26. added
    Status: stale-warningIssues and PRs flagged as stale which are due to be closed if no indication otherwise
    on Feb 18, 2026
  27. thisisnic commented on Feb 18, 2026

    @thisisnic
    Member

    This issue has been marked as stale because it has had no activity in the past 2 years. Please remove the stale label or comment below, or this issue will be closed in 14 days.

  28. removed
    Status: stale-warningIssues and PRs flagged as stale which are due to be closed if no indication otherwise
    on Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions