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

Add spec compliant histogram API while supporting legacy extension API #1793

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
13 changes: 11 additions & 2 deletions include/oneapi/dpl/pstl/histogram_extension_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,19 @@ namespace oneapi
{
namespace dpl
{

template <typename _ExecutionPolicy, typename _RandomAccessIterator1, typename _Size, typename _RandomAccessIterator2,
typename _ValueType = typename ::std::iterator_traits<_RandomAccessIterator1>::value_type>
typename = void>
oneapi::dpl::__internal::__enable_if_execution_policy<_ExecutionPolicy, _RandomAccessIterator2>
histogram(_ExecutionPolicy&& exec, _RandomAccessIterator1 first, _RandomAccessIterator1 last, _Size num_bins,
typename std::iterator_traits<_RandomAccessIterator1>::value_type first_bin_min_val,
typename std::iterator_traits<_RandomAccessIterator1>::value_type last_bin_max_val,
_RandomAccessIterator2 histogram_first);

template <typename _ExecutionPolicy, typename _RandomAccessIterator1, typename _Size, typename _RandomAccessIterator2,
typename _ValueType>
std::enable_if_t<oneapi::dpl::execution::is_execution_policy_v<std::decay_t<_ExecutionPolicy>> &&
!std::is_same_v<_ValueType, typename std::iterator_traits<_RandomAccessIterator1>::value_type>,
_RandomAccessIterator2>
histogram(_ExecutionPolicy&& exec, _RandomAccessIterator1 first, _RandomAccessIterator1 last, _Size num_bins,
_ValueType first_bin_min_val, _ValueType last_bin_max_val, _RandomAccessIterator2 histogram_first);

Expand Down
26 changes: 25 additions & 1 deletion include/oneapi/dpl/pstl/histogram_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,32 @@ __pattern_histogram(_Tag, _ExecutionPolicy&& exec, _RandomAccessIterator1 __firs
} // namespace __internal

template <typename _ExecutionPolicy, typename _RandomAccessIterator1, typename _Size, typename _RandomAccessIterator2,
typename _ValueType>
typename> //final unused template param is to avoid breaking change from original API (below)
oneapi::dpl::__internal::__enable_if_execution_policy<_ExecutionPolicy, _RandomAccessIterator2>
histogram(_ExecutionPolicy&& exec, _RandomAccessIterator1 first, _RandomAccessIterator1 last, _Size num_bins,
typename std::iterator_traits<_RandomAccessIterator1>::value_type first_bin_min_val,
typename std::iterator_traits<_RandomAccessIterator1>::value_type last_bin_max_val,
_RandomAccessIterator2 histogram_first)
{
using _BoundaryType = typename std::iterator_traits<_RandomAccessIterator1>::value_type;
const auto __dispatch_tag = oneapi::dpl::__internal::__select_backend(exec, first, histogram_first);

oneapi::dpl::__internal::__pattern_histogram(
__dispatch_tag, std::forward<_ExecutionPolicy>(exec), first, last, num_bins,
oneapi::dpl::__internal::__evenly_divided_binhash<_BoundaryType>(first_bin_min_val, last_bin_max_val, num_bins),
histogram_first);
return histogram_first + num_bins;
}

// This overload is provided to support an extension to the oneDPL specification to support the original implementation
// of the histogram API, where the boundary type _ValueType could differ from the value type of the input iterator,
// and required `operator<` and `operator<=` to be defined between _ValueType and
// std::iterator_traits<_RandomAccessIterator1>::value_type rather than enforcing they were the same type
template <typename _ExecutionPolicy, typename _RandomAccessIterator1, typename _Size, typename _RandomAccessIterator2,
typename _ValueType>
std::enable_if_t<oneapi::dpl::execution::is_execution_policy_v<std::decay_t<_ExecutionPolicy>> &&
!std::is_same_v<_ValueType, typename std::iterator_traits<_RandomAccessIterator1>::value_type>,
_RandomAccessIterator2>
histogram(_ExecutionPolicy&& exec, _RandomAccessIterator1 first, _RandomAccessIterator1 last, _Size num_bins,
_ValueType first_bin_min_val, _ValueType last_bin_max_val, _RandomAccessIterator2 histogram_first)
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if keeping the old variant of the function available sort of defeats the purpose of adding the new one, as Dan wrote in a comment to the specification PR:

I think it is better for force users to provide bounds which are convertible to the value_type of the input sequence, and do the conversion once when accepting the arguments rather than each time in the comparison operator.

If this overload is kept under the condition of type not being the same as the iterator value type, it will be selected even for the cases where we would want to have the conversion done only once. If the condition is changed to that of type not being convertible, will the code even work for such types? Or should the condition be for types that are not implicitly convertible to the desired one, while still explicitly convertible?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I need to experiment here a little to see if there is a better check. I think if we check !std::is_convertible instead, I think it should still work.

Copy link
Contributor Author

@danhoeflinger danhoeflinger Aug 28, 2024

Choose a reason for hiding this comment

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

Technically, I believe this works with !std::is_convertible_v in place of !istd::is_same_v in that all the cases which build and run before still build and run in the new version as well.

However, the unused and defaulted template parameter feels a little more hacky with this version. With the !std::is_same_v version, we know that the only time the spec compliant signature is picked, the types are the same, so doing this conversion is a no-op. Alternately, with !std::is_convertible_v, if someone explicitly lists out their parameter list, including a _ValueType which is convertible but not the same as the input iterator value type to match the old signature, we would still pick the new spec compliant overload and do the conversion.

I have an improved version in a sandbox godbolt which adds a (sigh) 4th overload, but manages to remove the hacky unused and defaulted template parameter from the spec compliant signature.

This version breaks up the extension overloads into two signatures:

  1. type not convertible to input iterator value type
    This is basically what we have here, but checking !std::is_convertible_v

  2. Explicit template list
    This makes the 4th template argument non-deducable, but has 5th and 6th template arguments to represent the types of the two boundary limit parameters

I'll get an commit in to update this PR with what it would look like tomorrow, its better, but I think I still prefer 1a or 1b.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated the code to implement the improved logic for supporting the extension API cases, and added tests to check these overloads specifically.

{
Expand Down
Loading