Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix hash join when the input tables have nulls on only one side #13120

Merged
merged 22 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from 11 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
18 changes: 15 additions & 3 deletions cpp/benchmarks/join/join.cu
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ void nvbench_inner_join(nvbench::state& state,
cudf::table_view const& right_input,
cudf::null_equality compare_nulls,
rmm::cuda_stream_view stream) {
cudf::hash_join hj_obj(left_input, compare_nulls, stream);
cudf::hash_join hj_obj(
left_input,
cudf::has_nested_nulls(left_input) || cudf::has_nested_nulls(right_input),
compare_nulls,
stream);
return hj_obj.inner_join(right_input, std::nullopt, stream);
};

Expand All @@ -44,7 +48,11 @@ void nvbench_left_join(nvbench::state& state,
cudf::table_view const& right_input,
cudf::null_equality compare_nulls,
rmm::cuda_stream_view stream) {
cudf::hash_join hj_obj(left_input, compare_nulls, stream);
cudf::hash_join hj_obj(
left_input,
cudf::has_nested_nulls(left_input) || cudf::has_nested_nulls(right_input),
compare_nulls,
stream);
return hj_obj.left_join(right_input, std::nullopt, stream);
};

Expand All @@ -61,7 +69,11 @@ void nvbench_full_join(nvbench::state& state,
cudf::table_view const& right_input,
cudf::null_equality compare_nulls,
rmm::cuda_stream_view stream) {
cudf::hash_join hj_obj(left_input, compare_nulls, stream);
cudf::hash_join hj_obj(
left_input,
cudf::has_nested_nulls(left_input) || cudf::has_nested_nulls(right_input),
compare_nulls,
stream);
return hj_obj.full_join(right_input, std::nullopt, stream);
};

Expand Down
11 changes: 7 additions & 4 deletions cpp/include/cudf/detail/join.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ struct hash_join {
hash_join& operator=(hash_join&&) = delete;

private:
bool const _is_empty; ///< true if `_hash_table` is empty
rmm::device_buffer const _composite_bitmask; ///< Bitmask to denote whether a row is valid
cudf::null_equality const _nulls_equal; ///< whether to consider nulls as equal
cudf::table_view _build; ///< input table to build the hash map
bool const _is_empty; ///< true if `_hash_table` is empty
bool const _has_nulls; ///< true if there is any nulls
ttnghia marked this conversation as resolved.
Show resolved Hide resolved
cudf::null_equality const _nulls_equal; ///< whether to consider nulls as equal
cudf::table_view _build; ///< input table to build the hash map
std::shared_ptr<cudf::experimental::row::equality::preprocessed_table>
_preprocessed_build; ///< input table preprocssed for row operators
map_type _hash_table; ///< hash table built on `_build`
Expand All @@ -89,10 +89,13 @@ struct hash_join {
* @throw cudf::logic_error if the number of rows in `build` table exceeds MAX_JOIN_SIZE.
*
* @param build The build table, from which the hash table is built.
* @param has_nulls Flag to indicate if the there exists any nulls in the `build` table or
* any probe table that will be used later for join.
ttnghia marked this conversation as resolved.
Show resolved Hide resolved
* @param compare_nulls Controls whether null join-key values should match or not.
* @param stream CUDA stream used for device memory operations and kernel launches.
*/
hash_join(cudf::table_view const& build,
bool has_nulls,
cudf::null_equality compare_nulls,
rmm::cuda_stream_view stream);

Expand Down
30 changes: 29 additions & 1 deletion cpp/include/cudf/join.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022, NVIDIA CORPORATION.
* Copyright (c) 2019-2023, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -288,6 +288,16 @@ class hash_join {
hash_join(cudf::table_view const& build,
null_equality compare_nulls,
rmm::cuda_stream_view stream = cudf::get_default_stream());
/**
* @copydoc hash_join
*
* @param has_nulls Flag to indicate if the there exists any nulls in the `build` table or
* any probe table that will be used later for join
*/
hash_join(cudf::table_view const& build,
bool has_nulls,
null_equality compare_nulls,
rmm::cuda_stream_view stream = cudf::get_default_stream());

/**
* Returns the row indices that can be used to construct the result of performing
Expand All @@ -300,6 +310,9 @@ class hash_join {
* @param mr Device memory resource used to allocate the returned table and columns' device
* memory.
*
* @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not
* constructed with null check.
*
* @return A pair of columns [`left_indices`, `right_indices`] that can be used to construct
* the result of performing an inner join between two tables with `build` and `probe`
* as the the join keys .
Expand All @@ -322,6 +335,9 @@ class hash_join {
* @param mr Device memory resource used to allocate the returned table and columns' device
* memory.
*
* @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not
* constructed with null check.
*
* @return A pair of columns [`left_indices`, `right_indices`] that can be used to construct
* the result of performing a left join between two tables with `build` and `probe`
* as the the join keys .
Expand All @@ -344,6 +360,9 @@ class hash_join {
* @param mr Device memory resource used to allocate the returned table and columns' device
* memory.
*
* @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not
* constructed with null check.
*
* @return A pair of columns [`left_indices`, `right_indices`] that can be used to construct
* the result of performing a full join between two tables with `build` and `probe`
* as the the join keys .
Expand All @@ -362,6 +381,9 @@ class hash_join {
* @param probe The probe table, from which the tuples are probed
* @param stream CUDA stream used for device memory operations and kernel launches
*
* @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not
* constructed with null check.
*
* @return The exact number of output when performing an inner join between two tables with
* `build` and `probe` as the the join keys .
*/
Expand All @@ -375,6 +397,9 @@ class hash_join {
* @param probe The probe table, from which the tuples are probed
* @param stream CUDA stream used for device memory operations and kernel launches
*
* @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not
* constructed with null check.
*
* @return The exact number of output when performing a left join between two tables with `build`
* and `probe` as the the join keys .
*/
Expand All @@ -390,6 +415,9 @@ class hash_join {
* @param mr Device memory resource used to allocate the intermediate table and columns' device
* memory.
*
* @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not
* constructed with null check.
*
* @return The exact number of output when performing a full join between two tables with `build`
* and `probe` as the the join keys .
*/
Expand Down
117 changes: 70 additions & 47 deletions cpp/src/join/hash_join.cu
Original file line number Diff line number Diff line change
Expand Up @@ -359,11 +359,11 @@ std::size_t get_full_join_size(

template <typename Hasher>
hash_join<Hasher>::hash_join(cudf::table_view const& build,
bool has_nulls,
cudf::null_equality compare_nulls,
rmm::cuda_stream_view stream)
: _is_empty{build.num_rows() == 0},
_composite_bitmask{
cudf::detail::bitmask_and(build, stream, rmm::mr::get_current_device_resource()).first},
: _has_nulls(has_nulls),
_is_empty{build.num_rows() == 0},
_nulls_equal{compare_nulls},
_hash_table{compute_hash_table_size(build.num_rows()),
cuco::empty_key{std::numeric_limits<hash_value_type>::max()},
Expand All @@ -381,11 +381,14 @@ hash_join<Hasher>::hash_join(cudf::table_view const& build,

if (_is_empty) { return; }

auto const row_bitmask =
cudf::detail::bitmask_and(build, stream, rmm::mr::get_current_device_resource()).first;
cudf::detail::build_join_hash_table(_build,
_preprocessed_build,
_hash_table,
_has_nulls,
_nulls_equal,
static_cast<bitmask_type const*>(_composite_bitmask.data()),
reinterpret_cast<bitmask_type const*>(row_bitmask.data()),
stream);
}

Expand Down Expand Up @@ -434,19 +437,21 @@ std::size_t hash_join<Hasher>::inner_join_size(cudf::table_view const& probe,
// Return directly if build table is empty
if (_is_empty) { return 0; }

CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe),
"Probe table has nulls while build table was not hashed with null check.");

auto const preprocessed_probe =
cudf::experimental::row::equality::preprocessed_table::create(probe, stream);

return cudf::detail::compute_join_output_size(
_build,
probe,
_preprocessed_build,
preprocessed_probe,
_hash_table,
cudf::detail::join_kind::INNER_JOIN,
cudf::has_nested_nulls(probe) || cudf::has_nested_nulls(_build),
_nulls_equal,
stream);
return cudf::detail::compute_join_output_size(_build,
probe,
_preprocessed_build,
preprocessed_probe,
_hash_table,
cudf::detail::join_kind::INNER_JOIN,
_has_nulls,
_nulls_equal,
stream);
}

template <typename Hasher>
Expand All @@ -458,19 +463,21 @@ std::size_t hash_join<Hasher>::left_join_size(cudf::table_view const& probe,
// Trivial left join case - exit early
if (_is_empty) { return probe.num_rows(); }

CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe),
"Probe table has nulls while build table was not hashed with null check.");
ttnghia marked this conversation as resolved.
Show resolved Hide resolved

auto const preprocessed_probe =
cudf::experimental::row::equality::preprocessed_table::create(probe, stream);

return cudf::detail::compute_join_output_size(
_build,
probe,
_preprocessed_build,
preprocessed_probe,
_hash_table,
cudf::detail::join_kind::LEFT_JOIN,
cudf::has_nested_nulls(probe) || cudf::has_nested_nulls(_build),
_nulls_equal,
stream);
return cudf::detail::compute_join_output_size(_build,
probe,
_preprocessed_build,
preprocessed_probe,
_hash_table,
cudf::detail::join_kind::LEFT_JOIN,
_has_nulls,
_nulls_equal,
stream);
}

template <typename Hasher>
Expand All @@ -483,19 +490,21 @@ std::size_t hash_join<Hasher>::full_join_size(cudf::table_view const& probe,
// Trivial left join case - exit early
if (_is_empty) { return probe.num_rows(); }

CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe),
"Probe table has nulls while build table was not hashed with null check.");

auto const preprocessed_probe =
cudf::experimental::row::equality::preprocessed_table::create(probe, stream);

return cudf::detail::get_full_join_size(
_build,
probe,
_preprocessed_build,
preprocessed_probe,
_hash_table,
cudf::has_nested_nulls(probe) || cudf::has_nested_nulls(_build),
_nulls_equal,
stream,
mr);
return cudf::detail::get_full_join_size(_build,
probe,
_preprocessed_build,
preprocessed_probe,
_hash_table,
_has_nulls,
_nulls_equal,
stream,
mr);
}

template <typename Hasher>
Expand All @@ -514,20 +523,22 @@ hash_join<Hasher>::probe_join_indices(cudf::table_view const& probe_table,

CUDF_EXPECTS(!_is_empty, "Hash table of hash join is null.");

CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe_table),
"Probe table has nulls while build table was not hashed with null check.");

auto const preprocessed_probe =
cudf::experimental::row::equality::preprocessed_table::create(probe_table, stream);
auto join_indices = cudf::detail::probe_join_hash_table(
_build,
probe_table,
_preprocessed_build,
preprocessed_probe,
_hash_table,
join,
cudf::has_nested_nulls(probe_table) || cudf::has_nested_nulls(_build),
_nulls_equal,
output_size,
stream,
mr);
auto join_indices = cudf::detail::probe_join_hash_table(_build,
probe_table,
_preprocessed_build,
preprocessed_probe,
_hash_table,
join,
_has_nulls,
_nulls_equal,
output_size,
stream,
mr);

if (join == cudf::detail::join_kind::FULL_JOIN) {
auto complement_indices = detail::get_left_join_indices_complement(
Expand All @@ -553,6 +564,9 @@ hash_join<Hasher>::compute_hash_join(cudf::table_view const& probe,
CUDF_EXPECTS(_build.num_columns() == probe.num_columns(),
"Mismatch in number of columns to be joined on");

CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe),
"Probe table has nulls while build table was not hashed with null check.");

if (is_trivial_join(probe, _build, join)) {
return std::pair(std::make_unique<rmm::device_uvector<size_type>>(0, stream, mr),
std::make_unique<rmm::device_uvector<size_type>>(0, stream, mr));
Expand All @@ -574,7 +588,16 @@ hash_join::~hash_join() = default;
hash_join::hash_join(cudf::table_view const& build,
null_equality compare_nulls,
rmm::cuda_stream_view stream)
: _impl{std::make_unique<const impl_type>(build, compare_nulls, stream)}
// If we cannot know beforehand about null existence then let's assume that there are nulls.
: hash_join(build, true /*has_nulls*/, compare_nulls, stream)
divyegala marked this conversation as resolved.
Show resolved Hide resolved
{
}

hash_join::hash_join(cudf::table_view const& build,
bool has_nulls,
null_equality compare_nulls,
rmm::cuda_stream_view stream)
: _impl{std::make_unique<const impl_type>(build, has_nulls, compare_nulls, stream)}
{
}

Expand Down
17 changes: 10 additions & 7 deletions cpp/src/join/join.cu
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022, NVIDIA CORPORATION.
* Copyright (c) 2019-2023, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -43,18 +43,19 @@ inner_join(table_view const& left_input,
rmm::mr::get_current_device_resource()); // temporary objects returned

// now rebuild the table views with the updated ones
auto const left = matched.second.front();
auto const right = matched.second.back();
auto const left = matched.second.front();
auto const right = matched.second.back();
auto const has_nulls = cudf::has_nested_nulls(left) || cudf::has_nested_nulls(right);

// For `inner_join`, we can freely choose either the `left` or `right` table to use for
// building/probing the hash map. Because building is typically more expensive than probing, we
// build the hash map from the smaller table.
if (right.num_rows() > left.num_rows()) {
cudf::hash_join hj_obj(left, compare_nulls, stream);
cudf::hash_join hj_obj(left, has_nulls, compare_nulls, stream);
auto [right_result, left_result] = hj_obj.inner_join(right, std::nullopt, stream, mr);
return std::pair(std::move(left_result), std::move(right_result));
} else {
cudf::hash_join hj_obj(right, compare_nulls, stream);
cudf::hash_join hj_obj(right, has_nulls, compare_nulls, stream);
return hj_obj.inner_join(left, std::nullopt, stream, mr);
}
}
Expand All @@ -76,8 +77,9 @@ left_join(table_view const& left_input,
// now rebuild the table views with the updated ones
table_view const left = matched.second.front();
table_view const right = matched.second.back();
auto const has_nulls = cudf::has_nested_nulls(left) || cudf::has_nested_nulls(right);

cudf::hash_join hj_obj(right, compare_nulls, stream);
cudf::hash_join hj_obj(right, has_nulls, compare_nulls, stream);
return hj_obj.left_join(left, std::nullopt, stream, mr);
}

Expand All @@ -98,8 +100,9 @@ full_join(table_view const& left_input,
// now rebuild the table views with the updated ones
table_view const left = matched.second.front();
table_view const right = matched.second.back();
auto const has_nulls = cudf::has_nested_nulls(left) || cudf::has_nested_nulls(right);

cudf::hash_join hj_obj(right, compare_nulls, stream);
cudf::hash_join hj_obj(right, has_nulls, compare_nulls, stream);
return hj_obj.full_join(left, std::nullopt, stream, mr);
}

Expand Down
Loading