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

[oneDPL] Add parallel range algorithms #569

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions source/elements/oneDPL/source/parallel_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -509,5 +509,9 @@ satisfy a given predicate, and stores the result to the output. Depending on the
considered element of the output sequence, that is, ``result + (end1 - start1)``.


.. toctree::

parallel_api/parallel_range_api.rst

.. _`C++ Standard`: https://isocpp.org/std/the-standard
.. _`SYCL`: https://registry.khronos.org/SYCL/specs/sycl-2020/html/sycl-2020.html
321 changes: 321 additions & 0 deletions source/elements/oneDPL/source/parallel_api/parallel_range_api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
.. SPDX-FileCopyrightText: Contributors to the oneAPI Specification project.
..
.. SPDX-License-Identifier: CC-BY-4.0

Parallel Ranges API
-------------------

oneDPL provides variations of algorithms that work with ranges defined in the `C++ Standard`_, 6th edition (C++20).
These algorithms execute according to a oneDPL execution policy supplied as the first argument, similarly to other
oneDPL algorithms.

The oneDPL parallel range algorithms rely on the functionality of C++20 and are not available in the code
compiled for earlier editions of the standard.

The parallel range algorithms reside in ``namespace oneapi::dpl::ranges``. Same as the range algorithm functions
defined by the `C++ standard` in ``namespace std::ranges``, they cannot be found by argument-dependent name lookup.
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want just require (on the specification level) that all of them are global callable objects (independently of where C++ standard goes)? With that requirement we give extra benefits for the users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Which specifically benefits would that give? Not theoretical but real ones, something that you can imagine being useful in practice.

Copy link
Contributor

Choose a reason for hiding this comment

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

passing algorithms as arguments to a function. This is the only practical difference I can see. It's guaranteed to work. With having "special function that..." it's not guaranteed to work. That's it

Copy link
Contributor Author

@akukanov akukanov Sep 2, 2024

Choose a reason for hiding this comment

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

Yes, that's a possibility. But I do not know why one would use it in practice, given that calling signatures of those algorithms are quite different and there seems no obvious reason why this would be useful.
In simple words, I see practical value of this being (nearly) absent.

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 have added a note about function object based implementations.


The following differences to the standard C++ range algorithms apply:

- The execution policy parameter is added.
- Output data sequences are defined as ranges, not iterators.
- Both input and output ranges must support random access.
- For a given algorithm, at least one of the input ranges as well as the output range must be bounded.
- ``for_each`` does not return its function object.
danhoeflinger marked this conversation as resolved.
Show resolved Hide resolved

Whole Sequence Operations
+++++++++++++++++++++++++

.. code:: cpp

// Defined in <oneapi/dpl/ranges>
akukanov marked this conversation as resolved.
Show resolved Hide resolved

namespace oneapi::dpl::ranges {

// all_of
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_unary_predicate< std::projected<std::ranges::iterator_t<R>, Proj> > Pred>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
bool all_of (ExecutionPolicy&& pol, R&& r, Pred pred, Proj proj = {});

// any_of
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_unary_predicate< std::projected<std::ranges::iterator_t<R>, Proj> > Pred>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
bool any_of (ExecutionPolicy&& pol, R&& r, Pred pred, Proj proj = {});

// none_of
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_unary_predicate< std::projected<std::ranges::iterator_t<R>, Proj> > Pred>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
bool none_of (ExecutionPolicy&& pol, R&& r, Pred pred, Proj proj = {});

// for_each
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirectly_unary_invocable< std::projected<std::ranges::iterator_t<R>, Proj> > Fn>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
std::ranges::borrowed_iterator_t<R>
for_each (ExecutionPolicy&& pol, R&& r, Fn f, Proj proj = {});

// count
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
typename T = std::projected_value_t<std::ranges::iterator_t<R>, Proj>>
danhoeflinger marked this conversation as resolved.
Show resolved Hide resolved
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R> &&
std::indirect_binary_predicate< std::ranges::equal_to,
std::projected<std::ranges::iterator_t<R>, Proj>,
const T* >
std::ranges::range_difference_t<R>
count (ExecutionPolicy&& pol, R&& r, const T& value, Proj proj = {});

// count_if
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_unary_predicate< std::projected<std::ranges::iterator_t<R>, Proj> > Pred>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
std::ranges::range_difference_t<R>
count_if (ExecutionPolicy&& pol, R&& r, Pred pred, Proj proj = {});

}

Element Search Operations
+++++++++++++++++++++++++

.. code:: cpp

// Defined in <oneapi/dpl/ranges>

namespace oneapi::dpl::ranges {

// find
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
typename T = std::projected_value_t<std::ranges::iterator_t<R>, Proj>>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R> &&
std::indirect_binary_predicate< std::ranges::equal_to,
std::projected<std::ranges::iterator_t<R>, Proj>,
const T* >
std::ranges::borrowed_iterator_t<R>
find (ExecutionPolicy&& pol, R&& r, const T& value, Proj proj = {});

// find_if
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_unary_predicate< std::projected<std::ranges::iterator_t<R>, Proj> > Pred>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
std::ranges::borrowed_iterator_t<R>
find_if (ExecutionPolicy&& pol, R&& r, Pred pred, Proj proj = {});

// find_if_not
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_unary_predicate< std::projected<std::ranges::iterator_t<R>, Proj> > Pred>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
std::ranges::borrowed_iterator_t<R>
find_if_not (ExecutionPolicy&& pol, R&& r, Pred pred, Proj proj = {});

// adjacent_find
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_binary_predicate< std::projected<std::ranges::iterator_t<R>, Proj>,
std::projected<std::ranges::iterator_t<R>, Proj> >
Pred = std::ranges::equal_to>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
std::ranges::borrowed_iterator_t<R>
adjacent_find (ExecutionPolicy&& pol, R&& r, Pred pred = {}, Proj proj = {});

// min_element
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_strict_weak_order< std::projected<std::ranges::iterator_t<R>, Proj> >
Comp = std::ranges::less>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
std::ranges::borrowed_iterator_t<R>
min_element (ExecutionPolicy&& pol, R&& r, Comp comp = {}, Proj proj = {});

// max_element
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_strict_weak_order< std::projected<std::ranges::iterator_t<R>, Proj> >
Comp = std::ranges::less>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
std::ranges::borrowed_iterator_t<R>
max_element (ExecutionPolicy&& pol, R&& r, Comp comp = {}, Proj proj = {});

}

Sequence Search and Comparison
++++++++++++++++++++++++++++++

.. code:: cpp

// Defined in <oneapi/dpl/ranges>

namespace oneapi::dpl::ranges {

// equal
template<typename ExecutionPolicy, std::ranges::random_access_range R1,
std::ranges::random_access_range R2, typename Pred = std::ranges::equal_to,
typename Proj1 = std::identity, typename Proj2 = std::identity>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
(std::ranges::sized_range<R1> || std::ranges::sized_range<R2>) &&
std::indirectly_comparable< std::ranges::iterator_t<R1>, std::ranges::iterator_t<R2>,
Pred, Proj1, Proj2 >
bool equal (ExecutionPolicy&& pol, R1&& r1, R2&& r2, Pred pred = {},
Proj1 proj1 = {}, Proj2 proj2 = {});

// search
template<typename ExecutionPolicy, std::ranges::random_access_range R1,
std::ranges::random_access_range R2, typename Pred = std::ranges::equal_to,
typename Proj1 = std::identity, typename Proj2 = std::identity>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R1> && std::ranges::sized_range<R2> &&
std::indirectly_comparable< std::ranges::iterator_t<R1>, std::ranges::iterator_t<R2>,
Pred, Proj1, Proj2 >
std::ranges::borrowed_subrange_t<R1>
search (ExecutionPolicy&& pol, R1&& r1, R2&& r2, Pred pred = {},
Proj1 proj1 = {}, Proj2 proj2 = {});

// search_n
danhoeflinger marked this conversation as resolved.
Show resolved Hide resolved
template<typename ExecutionPolicy, std::ranges::random_access_range R,
typename Pred = std::ranges::equal_to, typename Proj = std::identity,
typename T = std::projected_value_t<std::ranges::iterator_t<R>, Proj>>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R> &&
std::indirectly_comparable< std::ranges::iterator_t<R>, const T*, Pred, Proj >
std::ranges::borrowed_subrange_t<R>
search_n (ExecutionPolicy&& pol, R&& r, std::ranges::range_difference_t<R> count,
const T& value, Pred pred = {}, Proj proj = {});

}

Sorting and Merge
+++++++++++++++++

.. code:: cpp

// Defined in <oneapi/dpl/ranges>

namespace oneapi::dpl::ranges {

// sort
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Comp = std::ranges::less, typename Proj = std::identity>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R> && std::sortable<std::ranges::iterator_t<R>, Comp, Proj>
std::ranges::borrowed_iterator_t<R>
sort (ExecutionPolicy&& pol, R&& r, Comp comp = {}, Proj proj = {});

// stable_sort
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Comp = std::ranges::less, typename Proj = std::identity>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R> && std::sortable<std::ranges::iterator_t<R>, Comp, Proj>
std::ranges::borrowed_iterator_t<R>
stable_sort (ExecutionPolicy&& pol, R&& r, Comp comp = {}, Proj proj = {});

// is_sorted
template <typename ExecutionPolicy, std::ranges::random_access_range R,
typename Proj = std::identity,
std::indirect_strict_weak_order< std::projected<std::ranges::iterator_t<R>, Proj> >
Comp = std::ranges::less>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R>
bool is_sorted (ExecutionPolicy&& pol, R&& r, Comp comp = {}, Proj proj = {});

// merge
template <typename ExecutionPolicy, std::ranges::random_access_range R1,
std::ranges::random_access_range R2, std::ranges::random_access_range OutR,
typename Comp = std::ranges::less, typename Proj1 = std::identity,
typename Proj2 = std::identity>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R1> && std::ranges::sized_range<R2> &&
std::ranges::sized_range<OutR> &&
std::mergeable<std::ranges::iterator_t<R1>, std::ranges::iterator_t<R2>,
std::ranges::iterator_t<OutR>, Comp, Proj1, Proj2>
std::ranges::merge_result<std::ranges::borrowed_iterator_t<R1>,
std::ranges::borrowed_iterator_t<R2>,
std::ranges::borrowed_iterator_t<OutR>>
merge (R1&& r1, R2&& r2, OutR&& result, Comp comp = {}, Proj1 proj1 = {}, Proj2 proj2 = {});

}

Mutating Operations
+++++++++++++++++++

.. code:: cpp

// Defined in <oneapi/dpl/ranges>

namespace oneapi::dpl::ranges {

// copy
template <typename ExecutionPolicy, std::ranges::random_access_range R,
std::ranges::random_access_range OutR>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R> && std::ranges::sized_range<OutR> &&
std::indirectly_copyable<std::ranges::iterator_t<R>, std::ranges::iterator_t<OutR>>
std::ranges::copy_result<std::ranges::borrowed_iterator_t<R>,
std::ranges::borrowed_iterator_t<OutR>>
copy (ExecutionPolicy&& pol, R&& r, OutR&& result);

// copy_if
template <typename ExecutionPolicy, std::ranges::random_access_range R,
std::ranges::random_access_range OutR, typename Proj = std::identity,
std::indirect_unary_predicate< std::projected<std::ranges::iterator_t<R>, Proj> > Pred>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R> && std::ranges::sized_range<OutR> &&
std::indirectly_copyable<std::ranges::iterator_t<R>, std::ranges::iterator_t<OutR>>
std::ranges::copy_if_result<std::ranges::borrowed_iterator_t<R>,
std::ranges::borrowed_iterator_t<OutR>>
copy_if (ExecutionPolicy&& pol, R&& r, OutR&& result, Pred pred, Proj proj = {});

// transform (unary)
template <typename ExecutionPolicy, std::ranges::random_access_range R,
std::ranges::random_access_range OutR, std::copy_constructible Fn,
typename Proj = std::identity>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
std::ranges::sized_range<R> && std::ranges::sized_range<OutR> &&
std::indirectly_writable< std::ranges::iterator_t<OutR>,
std::indirect_result_t<Fn&, std::projected<std::ranges::iterator_t<R>, Proj>> >
std::ranges::unary_transform_result<std::ranges::borrowed_iterator_t<R>,
std::ranges::borrowed_iterator_t<OutR>>
transform (ExecutionPolicy&& pol, R&& r, OutR&& result, Fn unary_op, Proj proj = {});

// transform (binary)
template <typename ExecutionPolicy, std::ranges::random_access_range R1,
std::ranges::random_access_range R2, std::ranges::random_access_range OutR,
std::copy_constructible Fn, typename Proj1 = std::identity,
typename Proj2 = std::identity>
requires oneapi::dpl::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
(std::ranges::sized_range<R1> || std::ranges::sized_range<R2>) &&
std::ranges::sized_range<OutR> &&
std::indirectly_writable< std::ranges::iterator_t<OutR>,
std::indirect_result_t<Fn&, std::projected<std::ranges::iterator_t<R1>, Proj1>,
std::projected<std::ranges::iterator_t<R2>, Proj2>> >
std::ranges::binary_transform_result<std::ranges::borrowed_iterator_t<R1>,
std::ranges::borrowed_iterator_t<R2>,
std::ranges::borrowed_iterator_t<OutR>>
transform (ExecutionPolicy&& pol, R1&& r1, R2&& r2, OutR&& result, Fn binary_op,
Proj1 proj1 = {}, Proj2 proj2 = {});

}

.. _`C++ Standard`: https://isocpp.org/std/the-standard
.. _`SYCL`: https://registry.khronos.org/SYCL/specs/sycl-2020/html/sycl-2020.html