Skip to content

[PERF]: Improve the performance of plc.io.parquet.read_parquet with prefetched parquet file metadata. - #23558

Open
TomAugspurger wants to merge 21 commits into
NVIDIA:mainfrom
TomAugspurger:tom/parquet-metadata-gil-perf
Open

[PERF]: Improve the performance of plc.io.parquet.read_parquet with prefetched parquet file metadata.#23558
TomAugspurger wants to merge 21 commits into
NVIDIA:mainfrom
TomAugspurger:tom/parquet-metadata-gil-perf

Conversation

@TomAugspurger

@TomAugspurger TomAugspurger commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

We've observed a performance penalty from enabling metadata prefetching in cudf-polars for data in local storage. We'd like to enable prefetching by default, since it's crucial for good performance with high-latency remote storage systems (S3). Also, you'd naively expect prefetching to help locally since you'd do less work (you avoid re-parsing the footer when reading different row groups out of the same file multiple times).

Over simplifying things, prefetching slowed things down because it's (surprisingly?) expensive to copy a FileMetaData object. This pain was compounded in cudf-polars because we run concurrent read_parquets in a thread pool, and the GIL was held for these expensive copies.

(Note: why are we copying in the first place? IIUC, it's because the parquet reader currently mutates something on the object in read_parquet, so it needs an owned FileMetaData.)

  • This PR moves FileMetaData footers into Python without holding the GIL. This lets concurrent prefetches happen more... concurrently. See https://github.com/user-attachments/assets/c0f34117-8483-413a-a3de-e101a0093c12 for an nsys profile screenshot showing GIL contention.

  • After this fix, prefetching is still a clear win on S3 but there's still a ~16% penalty on NVMe. Rather than holding off entirely, this PR enables prefetching by default only for remote URIs (s3://, gs://, etc.). And queries that mix remote and local reads will only prefetch for the remote reads if metedata prefetching is unset (default).

Benchmarks (SF100, all 22 TPC-H queries, 256 kvikio threads):

Storage prefetch Total Mean
NVMe off 9.47s
NVMe on 10.95s (+15.6%)
S3 off 75.66s
S3 on 54.98s (-27.3%)

@TomAugspurger TomAugspurger added Performance Performance related issue improvement Improvement / enhancement to an existing function pylibcudf Issues specific to the pylibcudf package labels Aug 5, 2026
@copy-pr-bot

copy-pr-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@github-actions github-actions Bot added libcudf Affects libcudf (C++/CUDA) code. Python Affects Python cuDF API. labels Aug 5, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Aug 5, 2026
@TomAugspurger

Copy link
Copy Markdown
Contributor Author

/ok to test 39cc045

@TomAugspurger TomAugspurger added the non-breaking Non-breaking change label Aug 5, 2026
@TomAugspurger

Copy link
Copy Markdown
Contributor Author

This change did help with the metadata prefetching. But I still see GIL contention in Scan nodes:

image

@TomAugspurger

Copy link
Copy Markdown
Contributor Author

/ok to test b4d6a87

This changes the ownership model of FileMetadata to own the footers via
unique_ptr and move them (under nogil) in the pylibcudf wrapper.

Additionally, we avoid an unnecessary copy in get_parquet_metadatas().
@TomAugspurger
TomAugspurger force-pushed the tom/parquet-metadata-gil-perf branch from b4d6a87 to 1ac2985 Compare August 6, 2026 13:20
@TomAugspurger

Copy link
Copy Markdown
Contributor Author

/ok to test 1ac2985

@TomAugspurger

Copy link
Copy Markdown
Contributor Author

/ok to test af347e8

When page indexes are present, cloning the parquet footer FileMetaData object
can be expensive. This slows down `read_parquet` when the user provides a
prefetched metadata object.

This PR adds a new `read_page_indexes` parameter to `read_parquet_footers`
that controls whether page indexes are materialized. The default is `True`
matching the existing behavior.
@TomAugspurger
TomAugspurger force-pushed the tom/parquet-metadata-gil-perf branch from 3d8de45 to b3775f5 Compare August 6, 2026 17:53
@TomAugspurger

Copy link
Copy Markdown
Contributor Author

/ok to test f5525b5

@TomAugspurger TomAugspurger changed the title [PERF]: Avoid deep copies with GIL in pylibcudf read_parquet_footers [PERF]: Improve the performance of read_parquet with prefetched parquet file metadata. Aug 6, 2026
@TomAugspurger TomAugspurger changed the title [PERF]: Improve the performance of read_parquet with prefetched parquet file metadata. [PERF]: Improve the performance of plc.io.parquet.read_parquet with prefetched parquet file metadata. Aug 6, 2026
Comment thread cpp/benchmarks/io/parquet/parquet_reader_metadata.cpp Outdated
@github-actions github-actions Bot added the cudf-polars Issues specific to cudf-polars label Aug 6, 2026
@TomAugspurger

Copy link
Copy Markdown
Contributor Author

/ok to test 0053fb4

@TomAugspurger

Copy link
Copy Markdown
Contributor Author

These might overlap conceptually with #23546.

Comment thread cpp/src/io/parquet/reader_impl_helpers.hpp Outdated
Comment thread cpp/include/cudf/io/detail/parquet.hpp Outdated
def version(self):
"""Get the file format version."""
return self.c_obj.version
return dereference(self.c_obj).version

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a __cinit__ that default-constructs self.c_obj? Otherwise it's null in any instance not produced by from_libcudf (e.g., via FileMetaData.__new__(FileMetaData)), and this will segfault (as will any other dereference(), including the one in hybrid_scan.pyx).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we have __init__ raising, I think it's not easily possible to make a FileMetadata except via from_libcudf. This is a typical pattern in the pylibcudf bindings IIRC.

# Set the engine-dependent default, but don't override any user-provided values
# in-memory or via the environment.
if "prefetch_file_metadata" not in user_parquet_options and (
os.environ.get(f"{ParquetOptions._env_prefix}__PREFETCH_FILE_METADATA")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have an equivalent fix-up in the else branch? Otherwise the config passes through the incoming non-dict options object, and test_parquet_options_object_passthrough only tests with prefetch_file_metadata=False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread python/cudf_polars/cudf_polars/utils/config.py Outdated
Comment thread cpp/include/cudf/io/parquet_metadata.hpp Outdated
Comment thread python/pylibcudf/tests/io/test_parquet.py Outdated
Comment thread cpp/benchmarks/io/parquet/parquet_reader_metadata.cpp Outdated
Comment thread python/cudf_polars/cudf_polars/dsl/utils/io.py Outdated
Comment thread cpp/include/cudf/io/detail/parquet.hpp Outdated
def version(self):
"""Get the file format version."""
return self.c_obj.version
return dereference(self.c_obj).version

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we have __init__ raising, I think it's not easily possible to make a FileMetadata except via from_libcudf. This is a typical pattern in the pylibcudf bindings IIRC.

Comment thread python/cudf_polars/cudf_polars/utils/config.py Outdated
@Matt711
Matt711 requested review from a team as code owners August 12, 2026 16:21
@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot Aug 12, 2026
@Matt711

Matt711 commented Aug 12, 2026

Copy link
Copy Markdown
Member

/ok to test 93bccd7

@copy-pr-bot

copy-pr-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@Matt711

Matt711 commented Aug 12, 2026

Copy link
Copy Markdown
Member

/ok to test 8b7563a

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

Labels

cudf-polars Issues specific to cudf-polars improvement Improvement / enhancement to an existing function libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change Performance Performance related issue pylibcudf Issues specific to the pylibcudf package Python Affects Python cuDF API.

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Enable cudf-polars parquet metadata prefetching by default on the cloud

6 participants