Skip to content

Commit c1b43a6

Browse files
authored
Use pinned memory in PQ reader to avoid pageable copies (#20820)
Contributes to #20722 This PR replaces the use of small host vectors with pinned vectors to avoid pageable copies and improve pipeline performance when reading parquet files using multiple threads (each using a separate non-blocking stream) Authors: - Muhammad Haseeb (https://github.com/mhaseeb123) Approvers: - Bradley Dice (https://github.com/bdice) - Nghia Truong (https://github.com/ttnghia) - Vukasin Milovanovic (https://github.com/vuule) URL: #20820
1 parent 3f85f62 commit c1b43a6

11 files changed

Lines changed: 146 additions & 64 deletions

cpp/benchmarks/io/parquet/experimental/parquet_dictionary_page_filter.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -94,7 +94,6 @@ std::vector<rmm::device_buffer> fetch_byte_ranges(
9494
return buffer;
9595
});
9696

97-
stream.synchronize_no_throw();
9897
return buffers;
9998
}
10099

cpp/src/bitmask/null_mask.cu

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2019-2025, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -205,7 +205,8 @@ void set_null_masks(cudf::host_span<bitmask_type*> bitmasks,
205205

206206
size_t average_nullmask_words = 0;
207207
size_t cumulative_null_mask_words = 0;
208-
auto h_number_of_mask_words = cudf::detail::make_host_vector<size_type>(num_bitmasks, stream);
208+
auto h_number_of_mask_words =
209+
cudf::detail::make_pinned_vector_async<size_type>(num_bitmasks, stream);
209210
thrust::tabulate(
210211
thrust::host, h_number_of_mask_words.begin(), h_number_of_mask_words.end(), [&](auto i) {
211212
CUDF_EXPECTS(begin_bits[i] >= 0, "Invalid range.");

cpp/src/io/parquet/experimental/hybrid_scan_preprocess.cu

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -10,8 +10,6 @@
1010

1111
#include <cudf/detail/iterator.cuh>
1212
#include <cudf/detail/nvtx/ranges.hpp>
13-
#include <cudf/detail/utilities/batched_memset.hpp>
14-
#include <cudf/detail/utilities/vector_factories.hpp>
1513
#include <cudf/io/parquet_schema.hpp>
1614
#include <cudf/types.hpp>
1715
#include <cudf/utilities/memory_resource.hpp>

cpp/src/io/parquet/experimental/page_index_filter.cu

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -1092,7 +1092,7 @@ thrust::host_vector<bool> aggregate_reader_metadata::compute_data_page_mask(
10921092
auto tree_levels_data = rmm::device_uvector<bool>(tree_level_offsets.back(), stream, mr);
10931093

10941094
// Pointers to each Fenwick tree level data
1095-
auto host_tree_level_ptrs = cudf::detail::make_host_vector<bool*>(num_levels, stream);
1095+
auto host_tree_level_ptrs = cudf::detail::make_pinned_vector_async<bool*>(num_levels, stream);
10961096
// Zeroth level is just the row mask itself
10971097
host_tree_level_ptrs[0] = const_cast<bool*>(row_mask.template begin<bool>()) + row_mask_offset;
10981098
std::for_each(
@@ -1122,7 +1122,11 @@ thrust::host_vector<bool> aggregate_reader_metadata::compute_data_page_mask(
11221122
// Search the Fenwick tree to see if there's a surviving row in each page's row range
11231123
auto const num_ranges = static_cast<cudf::size_type>(page_row_offsets.size() - 1);
11241124
rmm::device_uvector<bool> device_data_page_mask(num_ranges, stream, mr);
1125-
auto page_offsets = cudf::detail::make_device_uvector_async(page_row_offsets, stream, mr);
1125+
// Use a pinned bounce buffer to avoid pageable h2d copy
1126+
auto host_page_offsets =
1127+
cudf::detail::make_pinned_vector_async<cudf::size_type>(page_row_offsets.size(), stream);
1128+
std::move(page_row_offsets.begin(), page_row_offsets.end(), host_page_offsets.begin());
1129+
auto page_offsets = cudf::detail::make_device_uvector_async(host_page_offsets, stream, mr);
11261130
thrust::transform(
11271131
rmm::exec_policy_nosync(stream),
11281132
thrust::counting_iterator(0),
@@ -1131,7 +1135,12 @@ thrust::host_vector<bool> aggregate_reader_metadata::compute_data_page_mask(
11311135
search_fenwick_tree_functor{fenwick_tree_level_ptrs.data(), page_offsets.data(), num_ranges});
11321136

11331137
// Copy over search results to host
1134-
auto host_results = cudf::detail::make_host_vector_async(device_data_page_mask, stream);
1138+
auto host_results =
1139+
cudf::detail::make_pinned_vector_async<bool>(device_data_page_mask.size(), stream);
1140+
cudf::detail::cuda_memcpy_async(
1141+
cudf::host_span<bool>(host_results.data(), host_results.size()),
1142+
cudf::device_span<bool const>(device_data_page_mask.data(), device_data_page_mask.size()),
1143+
stream);
11351144
auto const total_pages = page_row_offsets.size() - num_columns;
11361145
auto data_page_mask = thrust::host_vector<bool>(total_pages, stream);
11371146
auto host_results_iter = host_results.begin();

cpp/src/io/parquet/predicate_pushdown.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55
#include "reader_impl_helpers.hpp"
@@ -426,11 +426,15 @@ std::optional<std::vector<std::vector<size_type>>> collect_filtered_row_group_in
426426
CUDF_EXPECTS(predicate.type().id() == cudf::type_id::BOOL8,
427427
"Filter expression must return a boolean column");
428428

429-
auto const host_bitmask = [&] {
430-
auto const num_bitmasks = num_bitmask_words(predicate.size());
429+
auto host_bitmask = [&] {
430+
std::size_t const num_bitmasks = num_bitmask_words(predicate.size());
431431
if (predicate.nullable()) {
432-
return cudf::detail::make_host_vector(
433-
device_span<bitmask_type const>(predicate.null_mask(), num_bitmasks), stream);
432+
auto bitmask = cudf::detail::make_pinned_vector_async<bitmask_type>(num_bitmasks, stream);
433+
cudf::detail::cuda_memcpy(
434+
cudf::host_span<bitmask_type>{bitmask.data(), num_bitmasks},
435+
cudf::device_span<bitmask_type const>{predicate.null_mask(), num_bitmasks},
436+
stream);
437+
return bitmask;
434438
} else {
435439
auto bitmask = cudf::detail::make_host_vector<bitmask_type>(num_bitmasks, stream);
436440
std::fill(bitmask.begin(), bitmask.end(), ~bitmask_type{0});
@@ -442,8 +446,12 @@ std::optional<std::vector<std::vector<size_type>>> collect_filtered_row_group_in
442446
0, [bitmask = host_bitmask.data()](auto bit_index) { return bit_is_set(bitmask, bit_index); });
443447

444448
// Return only filtered row groups based on predicate
445-
auto const is_row_group_required = cudf::detail::make_host_vector(
446-
device_span<uint8_t const>(predicate.data<uint8_t>(), predicate.size()), stream);
449+
auto is_row_group_required =
450+
cudf::detail::make_pinned_vector_async<uint8_t>(predicate.size(), stream);
451+
cudf::detail::cuda_memcpy(
452+
cudf::host_span<uint8_t>(is_row_group_required.data(), predicate.size()),
453+
cudf::device_span<uint8_t const>(predicate.data<uint8_t>(), predicate.size()),
454+
stream);
447455

448456
// Return if all are required, or all are nulls.
449457
if (predicate.null_count() == predicate.size() or std::all_of(is_row_group_required.cbegin(),

cpp/src/io/parquet/reader_impl.cpp

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ void reader_impl::decode_page_data(read_mode mode, size_t skip_rows, size_t num_
184184
if (has_strings) {
185185
// Host vector to initialize the initial string offsets
186186
auto host_offsets_vector =
187-
cudf::detail::make_host_vector<size_t>(_input_columns.size(), _stream);
187+
cudf::detail::make_pinned_vector_async<size_t>(_input_columns.size(), _stream);
188188
std::fill(
189189
host_offsets_vector.begin(), host_offsets_vector.end(), std::numeric_limits<size_t>::max());
190190
// Initialize the initial string offsets vector from the host vector
@@ -397,7 +397,12 @@ void reader_impl::decode_page_data(read_mode mode, size_t skip_rows, size_t num_
397397
update_output_nullmasks_for_pruned_pages(_subpass_page_mask, skip_rows, num_rows);
398398

399399
// Copy over initial string offsets from device
400-
auto h_initial_str_offsets = cudf::detail::make_host_vector_async(initial_str_offsets, _stream);
400+
auto h_initial_str_offsets =
401+
cudf::detail::make_pinned_vector_async<size_t>(initial_str_offsets.size(), _stream);
402+
cudf::detail::cuda_memcpy_async(
403+
cudf::host_span<size_t>(h_initial_str_offsets.data(), initial_str_offsets.size()),
404+
cudf::device_span<size_t const>(initial_str_offsets.data(), initial_str_offsets.size()),
405+
_stream);
401406

402407
if (auto const error = error_code.value_sync(_stream); error != 0) {
403408
CUDF_FAIL("Parquet data decode failed with code(s) " + kernel_error::to_string(error));
@@ -427,14 +432,14 @@ void reader_impl::decode_page_data(read_mode mode, size_t skip_rows, size_t num_
427432

428433
// the final offset for a list at level N is the size of it's child
429434
size_type const offset = child.type.id() == type_id::LIST ? child.size - 1 : child.size;
430-
out_buffers.emplace_back(static_cast<size_type*>(out_buf.data()) + (out_buf.size - 1));
431-
final_offsets.emplace_back(offset);
435+
out_buffers.push_back(static_cast<size_type*>(out_buf.data()) + (out_buf.size - 1));
436+
final_offsets.push_back(offset);
432437
out_buf.user_data |= PARQUET_COLUMN_BUFFER_FLAG_LIST_TERMINATED;
433438
} else if (out_buf.type.id() == type_id::STRING) {
434439
// only if it is not a large strings column
435440
if (std::cmp_less_equal(col_string_sizes[idx], strings::detail::get_offset64_threshold())) {
436-
out_buffers.emplace_back(static_cast<size_type*>(out_buf.data()) + out_buf.size);
437-
final_offsets.emplace_back(static_cast<size_type>(col_string_sizes[idx]));
441+
out_buffers.push_back(static_cast<size_type*>(out_buf.data()) + out_buf.size);
442+
final_offsets.push_back(static_cast<size_type>(col_string_sizes[idx]));
438443
}
439444
// Nested large strings column
440445
else if (input_col.nesting_depth() > 0) {
@@ -446,7 +451,13 @@ void reader_impl::decode_page_data(read_mode mode, size_t skip_rows, size_t num_
446451
}
447452
}
448453
// Write the final offsets for list and string columns in a batched manner
449-
write_final_offsets(final_offsets, out_buffers, _stream);
454+
auto pinned_final_offsets =
455+
cudf::detail::make_pinned_vector_async<cudf::size_type>(final_offsets.size(), _stream);
456+
auto pinned_out_buffers =
457+
cudf::detail::make_pinned_vector_async<cudf::size_type*>(out_buffers.size(), _stream);
458+
std::move(final_offsets.begin(), final_offsets.end(), pinned_final_offsets.begin());
459+
std::move(out_buffers.begin(), out_buffers.end(), pinned_out_buffers.begin());
460+
write_final_offsets(pinned_final_offsets, pinned_out_buffers, _stream);
450461

451462
// update null counts in the final column buffers
452463
for (size_t idx = 0; idx < subpass.pages.size(); idx++) {
@@ -928,9 +939,9 @@ void reader_impl::update_output_nullmasks_for_pruned_pages(cudf::host_span<bool
928939
auto page_and_mask_begin =
929940
thrust::make_zip_iterator(cuda::std::make_tuple(pages.host_begin(), page_mask.begin()));
930941

931-
auto null_masks = std::vector<bitmask_type*>{};
932-
auto begin_bits = std::vector<cudf::size_type>{};
933-
auto end_bits = std::vector<cudf::size_type>{};
942+
auto host_null_masks = std::vector<bitmask_type*>{};
943+
auto host_begin_bits = std::vector<cudf::size_type>{};
944+
auto host_end_bits = std::vector<cudf::size_type>{};
934945

935946
std::for_each(
936947
page_and_mask_begin, page_and_mask_begin + pages.size(), [&](auto const& page_and_mask_pair) {
@@ -983,9 +994,9 @@ void reader_impl::update_output_nullmasks_for_pruned_pages(cudf::host_span<bool
983994
cols = &out_buf.children;
984995
if (out_buf.user_data & PARQUET_COLUMN_BUFFER_FLAG_HAS_LIST_PARENT) { continue; }
985996
// Add the nullmask and bit bounds to corresponding lists
986-
null_masks.emplace_back(out_buf.null_mask());
987-
begin_bits.emplace_back(start_row);
988-
end_bits.emplace_back(end_row);
997+
host_null_masks.emplace_back(out_buf.null_mask());
998+
host_begin_bits.emplace_back(start_row);
999+
host_end_bits.emplace_back(end_row);
9891000

9901001
// Increment the null count by the number of rows in this page
9911002
out_buf.null_count() += page.num_rows;
@@ -995,9 +1006,20 @@ void reader_impl::update_output_nullmasks_for_pruned_pages(cudf::host_span<bool
9951006
// Min number of nullmasks to use bulk update optimally
9961007
constexpr auto min_nullmasks_for_bulk_update = 32;
9971008

1009+
// Use a bounce buffer to avoid pageable copies
1010+
auto null_masks =
1011+
cudf::detail::make_pinned_vector_async<bitmask_type*>(host_null_masks.size(), _stream);
1012+
auto begin_bits =
1013+
cudf::detail::make_pinned_vector_async<cudf::size_type>(host_begin_bits.size(), _stream);
1014+
auto end_bits =
1015+
cudf::detail::make_pinned_vector_async<cudf::size_type>(host_end_bits.size(), _stream);
1016+
std::move(host_null_masks.begin(), host_null_masks.end(), null_masks.begin());
1017+
std::move(host_begin_bits.begin(), host_begin_bits.end(), begin_bits.begin());
1018+
std::move(host_end_bits.begin(), host_end_bits.end(), end_bits.begin());
1019+
9981020
// Bulk update the nullmasks if the number of pages is above the threshold
9991021
if (null_masks.size() >= min_nullmasks_for_bulk_update) {
1000-
auto valids = cudf::detail::make_host_vector<bool>(null_masks.size(), _stream);
1022+
auto valids = cudf::detail::make_pinned_vector_async<bool>(null_masks.size(), _stream);
10011023
std::fill(valids.begin(), valids.end(), false);
10021024
cudf::set_null_masks_safe(null_masks, begin_bits, end_bits, valids, _stream);
10031025
}

cpp/src/io/parquet/reader_impl_chunking.cu

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -327,7 +327,11 @@ void reader_impl::setup_next_subpass(read_mode mode)
327327
subpass.pages = subpass.page_buf;
328328
}
329329

330-
auto const h_spans = cudf::detail::make_host_vector_async(page_indices, _stream);
330+
auto h_spans = cudf::detail::make_pinned_vector_async<page_span>(page_indices.size(), _stream);
331+
cudf::detail::cuda_memcpy_async(
332+
cudf::host_span<page_span>{h_spans.data(), page_indices.size()},
333+
cudf::device_span<page_span const>{page_indices.data(), page_indices.size()},
334+
_stream);
331335
subpass.pages.device_to_host_async(_stream);
332336

333337
_stream.synchronize();
@@ -690,7 +694,12 @@ void reader_impl::set_subpass_page_mask()
690694
}
691695

692696
// Use the pass page index mask to gather the subpass page mask from the pass level page mask
693-
auto const host_page_src_index = cudf::detail::make_host_vector(subpass->page_src_index, _stream);
697+
auto host_page_src_index =
698+
cudf::detail::make_pinned_vector_async<size_t>(subpass->page_src_index.size(), _stream);
699+
cudf::detail::cuda_memcpy(
700+
cudf::host_span<size_t>{host_page_src_index.data(), subpass->page_src_index.size()},
701+
cudf::device_span<size_t const>{subpass->page_src_index.data(), subpass->page_src_index.size()},
702+
_stream);
694703
thrust::gather(thrust::seq,
695704
host_page_src_index.begin(),
696705
host_page_src_index.end(),

0 commit comments

Comments
 (0)