Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 43 additions & 34 deletions cpp/src/io/parquet/experimental/page_index_filter.cu
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,13 @@ struct page_stats_caster : public stats_caster_base {

/**
* @brief Computes host side data including page row offsets, column chunk page offsets, and host
* columns containing page-level min, max and (optional) is_null statistics for a column
* columns containing page-level min, max and (optional) all-null statistics for a column
Comment thread
pmattione-nvidia marked this conversation as resolved.
*
* @param schema_idx Column schema index
* @param dtype Column data type
* @param stream CUDA stream
* @return A tuple of page row offsets, column chunk page offsets, and host columns containing
* page-level min, max and (optional) is_null statistics
* page-level min, max and (optional) all-null statistics
Comment thread
pmattione-nvidia marked this conversation as resolved.
*/
template <typename T>
[[nodiscard]] auto compute_host_data(cudf::size_type schema_idx,
Expand All @@ -300,11 +300,13 @@ struct page_stats_caster : public stats_caster_base {

auto const total_pages = col_chunk_page_offsets.back();

// Create host columns with page-level min, max and optionally is_null statistics
// Create host columns with page-level min, max and optionally all-null statistics. The
// all-null column is true only when every value in the page is null, false when none are, and
// null when only some are, which is what lets it answer both IS_NULL and IS NOT NULL.
host_column<T> min(total_pages, stream);
host_column<T> max(total_pages, stream);
std::optional<host_column<bool>> is_null;
if (has_is_null_operator) { is_null = host_column<bool>(total_pages, stream); }
std::optional<host_column<bool>> all_null;
if (has_is_null_operator) { all_null = host_column<bool>(total_pages, stream); }

// Compute timestamp scale factor for precision conversion
auto const ts_scale = [&] {
Expand Down Expand Up @@ -339,6 +341,18 @@ struct page_stats_caster : public stats_caster_base {
auto const num_pages_in_colchunk = column_index.min_values.size();
auto const page_offset_in_colchunk = col_chunk_page_offsets[page_offset_idx++];

if (has_is_null_operator) {
CUDF_EXPECTS(column_index.null_pages.size() == num_pages_in_colchunk,
"Number of null page flags must match the number of pages in the column "
"chunk",
std::invalid_argument);
CUDF_EXPECTS(not column_index.null_counts.has_value() or
column_index.null_counts.value().size() == num_pages_in_colchunk,
"Number of page null counts must match the number of pages in the column "
"chunk",
std::invalid_argument);
}

// For all pages in this column chunk
std::for_each(
cuda::counting_iterator<std::size_t>{0},
Expand All @@ -353,22 +367,24 @@ struct page_stats_caster : public stats_caster_base {
if (has_is_null_operator) {

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.

Now that we're encountering this branch in a lot more places than we used to, is it worth adding any extra error checking (e.g. bounds checking)? I don't know the code paths that lead here well enough to know if there's any extra risk now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

column_page_idx should be safe, but I suppose null_pages and null_counts may not be sized properly if the file was corrupt. Added checks for those.

// Check if the page is completely null
if (column_index.null_pages[page_idx]) {
is_null->val[column_page_idx] = true;
all_null->val[column_page_idx] = true;
return;
}
// Check if the page doesn't have a null count
if (not column_index.null_counts.has_value()) {
is_null->set_index(column_page_idx, std::nullopt, {});
all_null->set_index(column_page_idx, std::nullopt, {});
return;
}
// Use the null count to determine if the page is completely null
auto const page_row_count =
page_row_offsets[column_page_idx + 1] - page_row_offsets[column_page_idx];
auto const& null_count = column_index.null_counts.value()[page_idx];
if (null_count == page_row_count) {
is_null->val[column_page_idx] = false;
} else if (null_count > 0 and null_count < page_row_count) {
is_null->set_index(column_page_idx, std::nullopt, {});
if (null_count == 0) {
all_null->val[column_page_idx] = false;
} else if (null_count < page_row_count) {
all_null->set_index(column_page_idx, std::nullopt, {});
} else if (null_count == page_row_count) {
all_null->val[column_page_idx] = true;
} else {
CUDF_FAIL("Invalid null count");
}
Expand All @@ -381,7 +397,7 @@ struct page_stats_caster : public stats_caster_base {
std::move(col_chunk_page_offsets),
std::move(min),
std::move(max),
std::move(is_null)};
std::move(all_null)};
}

/**
Expand Down Expand Up @@ -563,7 +579,7 @@ struct page_stats_to_row_mask_converter : public page_stats_caster {
auto page_stats_table = cudf::table(std::move(columns));
// Converts AST to StatsAST with reference to min, max columns in above `stats_table`.
parquet::detail::stats_expression_converter const stats_expr{
filter.get(), std::span{&dtype, 1}, has_is_null_operator, stream};
filter.get(), std::span{&dtype, 1}, stream};

// Filter the input table using AST expression and return the (BOOL8) predicate column.
auto const page_mask = cudf::detail::compute_column(page_stats_table,
Expand Down Expand Up @@ -864,7 +880,7 @@ std::unique_ptr<cudf::column> aggregate_reader_metadata::build_row_mask_with_pag
auto const num_columns = output_dtypes.size();

// Get a boolean mask indicating which columns will participate in stats based filtering
auto const [stats_columns_mask, has_is_null_operator] =
auto const stats_columns_mask =
parquet::detail::stats_columns_collector{filter.get(), output_dtypes}.get_stats_columns_mask();

// Return early if no columns will participate in stats based page filtering
Expand Down Expand Up @@ -897,10 +913,8 @@ std::unique_ptr<cudf::column> aggregate_reader_metadata::build_row_mask_with_pag

// Optimization for single column filter: Directly build the row mask from page statistics
if (num_columns == 1) {
page_stats_to_row_mask_converter const stats_col{static_cast<size_type>(total_rows),
per_file_metadata,
row_group_indices,
has_is_null_operator};
page_stats_to_row_mask_converter const stats_col{
static_cast<size_type>(total_rows), per_file_metadata, row_group_indices, true};
return cudf::type_dispatcher<dispatch_storage_type>(output_dtypes.front(),
stats_col,
output_column_schemas.front(),
Expand All @@ -916,7 +930,7 @@ std::unique_ptr<cudf::column> aggregate_reader_metadata::build_row_mask_with_pag
page_stats_caster const stats_col{.total_rows = static_cast<size_type>(total_rows),
.per_file_metadata = per_file_metadata,
.row_group_indices = row_group_indices,
.has_is_null_operator = has_is_null_operator};
.has_is_null_operator = true};
Comment thread
pmattione-nvidia marked this conversation as resolved.

std::vector<std::unique_ptr<column>> page_stats_columns;
std::for_each(
Expand All @@ -943,32 +957,27 @@ std::unique_ptr<cudf::column> aggregate_reader_metadata::build_row_mask_with_pag
0,
stream,
cudf::get_current_device_resource_ref()));
if (has_is_null_operator) {
page_stats_columns.push_back(cudf::make_numeric_column(
data_type{cudf::type_id::BOOL8},
total_rows,
rmm::device_buffer{0, stream, cudf::get_current_device_resource_ref()},
0,
stream,
cudf::get_current_device_resource_ref()));
}
page_stats_columns.push_back(cudf::make_numeric_column(
data_type{cudf::type_id::BOOL8},
total_rows,
rmm::device_buffer{0, stream, cudf::get_current_device_resource_ref()},
0,
stream,
cudf::get_current_device_resource_ref()));
return;
}
auto [min_col, max_col, is_null_col] = cudf::type_dispatcher<dispatch_storage_type>(
dtype, stats_col, schema_idx, dtype, stream, cudf::get_current_device_resource_ref());
page_stats_columns.push_back(std::move(min_col));
page_stats_columns.push_back(std::move(max_col));
if (has_is_null_operator) {
CUDF_EXPECTS(is_null_col.has_value(), "is_null host column must be present");
page_stats_columns.push_back(std::move(is_null_col.value()));
}
CUDF_EXPECTS(is_null_col.has_value(), "is_null host column must be present");
page_stats_columns.push_back(std::move(is_null_col.value()));
});

auto page_stats_table = cudf::table(std::move(page_stats_columns));

// Converts AST to StatsAST with reference to min, max columns in above `stats_table`.
parquet::detail::stats_expression_converter const stats_expr{
filter.get(), output_dtypes, has_is_null_operator, stream};
parquet::detail::stats_expression_converter const stats_expr{filter.get(), output_dtypes, stream};

// Filter the input table using AST expression and return the (BOOL8) predicate column.
return cudf::detail::compute_column(
Expand Down
31 changes: 13 additions & 18 deletions cpp/src/io/parquet/predicate_pushdown.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ std::optional<std::vector<std::vector<size_type>>> aggregate_reader_metadata::ap
auto mr = cudf::get_current_device_resource_ref();

// Get a boolean mask indicating which columns can participate in stats based filtering
auto const [stats_columns_mask, has_is_null_operator] =
auto const stats_columns_mask =
stats_columns_collector{filter.get(), output_dtypes}.get_stats_columns_mask();

// Return early if no columns will participate in stats based filtering
Expand All @@ -90,14 +90,14 @@ std::optional<std::vector<std::vector<size_type>>> aggregate_reader_metadata::ap
}

// Converts Column chunk statistics to a table
// where min(col[i]) = columns[i*2], max(col[i])=columns[i*2+1]
// For each column, it contains #sources * #column_chunks_per_src rows
// where min(col[i]) = columns[i*3], max(col[i]) = columns[i*3+1], is_null(col[i]) =
// columns[i*3+2] For each column, it contains #sources * #column_chunks_per_src rows
std::vector<std::unique_ptr<column>> columns;
row_group_stats_caster const stats_col{
.total_row_groups = static_cast<size_type>(total_row_groups),
.per_file_metadata = per_file_metadata,
.row_group_indices = input_row_group_indices,
.has_is_null_operator = has_is_null_operator};
.has_is_null_operator = true};

for (size_t col_idx = 0; col_idx < output_dtypes.size(); col_idx++) {
auto const schema_idx = output_column_schemas[col_idx];
Expand All @@ -118,14 +118,12 @@ std::optional<std::vector<std::vector<size_type>>> aggregate_reader_metadata::ap
0,
stream,
mr));
if (has_is_null_operator) {
columns.push_back(cudf::make_numeric_column(data_type{cudf::type_id::BOOL8},
total_row_groups,
rmm::device_buffer{0, stream, mr},
0,
stream,
mr));
}
columns.push_back(cudf::make_numeric_column(data_type{cudf::type_id::BOOL8},
total_row_groups,
rmm::device_buffer{0, stream, mr},
0,
stream,
mr));
continue;
}
// Map each filter column's zeroth-source schema index into every source's schema tree.
Expand All @@ -140,16 +138,13 @@ std::optional<std::vector<std::vector<size_type>>> aggregate_reader_metadata::ap
dtype, stats_col, per_source_schema_indices, dtype, stream, mr);
columns.push_back(std::move(min_col));
columns.push_back(std::move(max_col));
if (has_is_null_operator) {
CUDF_EXPECTS(is_null_col.has_value(), "is_null column must be present");
columns.push_back(std::move(is_null_col.value()));
}
CUDF_EXPECTS(is_null_col.has_value(), "is_null column must be present");
columns.push_back(std::move(is_null_col.value()));
}
auto stats_table = cudf::table(std::move(columns));

// Converts AST to StatsAST with reference to min, max columns in above `stats_table`.
stats_expression_converter const stats_expr{
filter.get(), output_dtypes, has_is_null_operator, stream};
stats_expression_converter const stats_expr{filter.get(), output_dtypes, stream};

// Filter stats table with StatsAST expression and collect filtered row group indices
return collect_filtered_row_group_indices(
Expand Down
6 changes: 6 additions & 0 deletions cpp/src/io/parquet/row_group_stats_helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ struct row_group_stats_caster : public stats_caster_base {
} else {
CUDF_FAIL("Invalid null count");
}
} else {
// Statistics without a null count say nothing about this chunk's nullability. The
// value array is allocated uninitialized and the null mask starts out all valid, so
// this entry has to be marked null; leaving it alone would let an uninitialized
// byte be read as an answer.
is_null->set_index(stats_idx, std::nullopt, {});
Comment thread
pmattione-nvidia marked this conversation as resolved.
}
}
} else {
Expand Down
Loading
Loading