Skip to content

[onedpl][ranges] L3 Range based algo API and implementation (memory group) #2292

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

Open
wants to merge 70 commits into
base: main
Choose a base branch
from

Conversation

MikeDvorskiy
Copy link
Contributor

[onedpl][ranges] L3 Range based algo API and implementation (memory group)

@MikeDvorskiy MikeDvorskiy marked this pull request as draft June 2, 2025 15:41
@MikeDvorskiy MikeDvorskiy force-pushed the dev/mdvorski/ranges_algo_L3_memory-origin_UXL branch 4 times, most recently from 606f4fa to 39d183b Compare June 3, 2025 13:31
@MikeDvorskiy MikeDvorskiy marked this pull request as ready for review June 3, 2025 15:18
@MikeDvorskiy MikeDvorskiy force-pushed the dev/mdvorski/ranges_algo_L3_memory-origin_UXL branch 3 times, most recently from 22f1c16 to 0ec2d06 Compare June 4, 2025 15:11
@MikeDvorskiy MikeDvorskiy force-pushed the dev/mdvorski/ranges_algo_L3_memory-origin_UXL branch from 0ec2d06 to 470f339 Compare June 10, 2025 14:46
int val1; //value initialization
int val2;

Elem_0(): val1() {}
Copy link
Contributor

@SergeyKopienko SergeyKopienko Jun 11, 2025

Choose a reason for hiding this comment

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

Can you explain in comments the idea of works with structure in which data is uninitialized and from this point may have any state?

std::ranges::borrowed_iterator_t<_R>
operator()(_ExecutionPolicy&& __exec, _R&& __r) const
{
const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec);
Copy link
Contributor

Choose a reason for hiding this comment

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

In another places I see the code like

const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec, __rng);

where we pass some __rng into __select_backend call.

From what it depends?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch, thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

There are more than one place like that, not all from this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Update: Actually, the original comment is not valid here.
"another places" - you mind the experimental code... In the product code we pass only policy into select_backend function.
In "product" code we have random access ranges checks via concepts in the template description. So, we dont need do it the second time by passing ranges into the select_backend function.

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably this mean we should move __select_backend function from the call

const auto __dispatch_tag = oneapi::dpl::__ranges::__select_backend(__exec, __rng);

into experimental namespace too?

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 don't see a reason for that...

Copy link
Contributor

Choose a reason for hiding this comment

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

So I see on our code two funtions:

namespace oneapi
{
namespace dpl
{
namespace __ranges
{

template <typename _KernelName, typename... _Ranges>
oneapi::dpl::__internal::__hetero_tag<oneapi::dpl::__internal::__device_backend_tag>
__select_backend(const execution::device_policy<_KernelName>&, _Ranges&&...)
{
    return {};
}

#if _ONEDPL_FPGA_DEVICE
template <unsigned int _Factor, typename _KernelName, typename... _Ranges>
oneapi::dpl::__internal::__hetero_tag<oneapi::dpl::__internal::__fpga_backend_tag>
__select_backend(const execution::fpga_policy<_Factor, _KernelName>&, _Ranges&&...)
{
    return {};
}
#endif

} // namespace __ranges
} // namespace dpl
} // namespace oneapi

In the calls from experimental code you pass ranges into it, from non-experimental code you don't pass ranges.
But in any case ranges never used inside the __select_backend function, as we may see.
Can you explain the overall idea of that?

Copy link
Contributor

@dmitriy-sobolev dmitriy-sobolev Jun 13, 2025

Choose a reason for hiding this comment

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

Related discussion: #1457 (comment). Mikhail's conclusion was to remove __select_backend accepting ranges when we remove the experimental range algorithms.

However, moving it into the experimental namespace looks reasonable to disambiguate different __select_backend but not critical in this PR.

Anyway, I suggest creating an issue to remove/refactor __select_backend with ranges in order not to forget about it. or doing that in a separate PR.


oneapi::dpl::uninitialized_fill(std::forward<_ExecutionPolicy>(__exec), __first, __last, __value);

return {__last};
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return {__last};
return __last;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

{} is written intentionally, because it is equivalent to std::ranges::borrowed_iterator_t<_R>{__last} (see the return type for the function).

Copy link
Contributor

Choose a reason for hiding this comment

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

Will we have compile error in the next case?

    return __last;

Copy link
Contributor

Choose a reason for hiding this comment

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

Just for note: practically we never have returns with this form for cases when we return one value, as I seen.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • return __last; tells the function returns an instance of "decltype(__last)"
  • return {__last}; tells the function returns an instance of new type (return type), which is created from __last.

Copy link
Contributor

@dmitriy-sobolev dmitriy-sobolev Jun 12, 2025

Choose a reason for hiding this comment

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

My two cents:

  • I like (1) when a function is short: no duplication, you see the return type and {} suggests that is it not the type of __last.
  • I do not like (2). It is as (1) but it does not benefit from {}. Adding {} is not that extra number of symbol we should avoid.
  • I do not like (3), because I think that having the return type in the function signature is more important.
  • I like (4) when a function is large.

It's mostly a matter of preference (and now looks like bikeshedding), but __pattern_uninitialized_fill is small, so I vote for (1).

Copy link
Contributor

@SergeyKopienko SergeyKopienko Jun 12, 2025

Choose a reason for hiding this comment

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

Probably it's a good topic to discuss shortly on our meeting.

UPD: the option (1) described at https://en.cppreference.com/w/cpp/language/list_initialization.html
And this variant has strongly defined semantic when make sense to apply in.
As far as we haven't compile errors here there is no reasons to apply it in the discussing cases I believe.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree here fully with @dmitriy-sobolev . (1) when short, (4) when large.

I think these options make the code most clear and readable, which is my rule of thumb.

Copy link
Contributor

Choose a reason for hiding this comment

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

After some discussion with @SergeyKopienko offline and exploring with godbolt, I realized that (1) is not explicitly invoking the constructor, but rather creating an initializer list, and then still using implicit construction of the return type from that initializer list.

https://godbolt.org/z/dTo84fooz

While not the exact same error message, (1) and (2) both don't work with explicit constructors. I guess (1) may give the perception that the type will be changing, (1) is not a shorthand equivalent to (4).

So, I guess perhaps I prefer (4) generally. For short functions, I think (1) and (2) are both fine, its probably not worth a whole lot more time discussing.

Copy link
Contributor

Choose a reason for hiding this comment

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

...from cppreference.com :

https://en.cppreference.com/w/cpp/language/return.html

2) Uses [copy-list-initialization](https://en.cppreference.com/w/cpp/language/list_initialization.html) to construct the return value of the function.

https://en.cppreference.com/w/cpp/language/list_initialization.html

8) in a return statement with a brace-enclosed initializer list used as the return expression and list-initialization initializes the returned object


oneapi::dpl::destroy(std::forward<_ExecutionPolicy>(__exec), __first, __last);

return {__last};
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return {__last};
return __last;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See my explanation above.

@MikeDvorskiy MikeDvorskiy force-pushed the dev/mdvorski/ranges_algo_L3_memory-origin_UXL branch 2 times, most recently from ab2357f to ee23b56 Compare June 11, 2025 15:59
@MikeDvorskiy MikeDvorskiy force-pushed the dev/mdvorski/ranges_algo_L3_memory-origin_UXL branch from ee23b56 to 9aad8b3 Compare June 12, 2025 10:28
…_tag_v<_Tag> || typename _Tag::__is_vector{}); and usage of some predefined functors"

This reverts commit 0c2f14c.
…t_symmetric_difference, high level API"

This reverts commit 42a98e8.
…anges::set_intersection high level API"

This reverts commit c3f437e.
int val1;
int val2;

Elem() { val1 = 1; }
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should have additional comments in the code why we don't initialize each field in constructors.

int val1;
int val2;

Elem_0(): val1() {} //val1 has a value initialization here
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should have additional comments in the code why we don't initialize each field in constructors.

Comment on lines +47 to +50
template<typename _R>
concept nothrow_random_access_range =
std::ranges::range<_R> && nothrow_random_access_iterator<std::ranges::iterator_t<_R>> &&
nothrow_sentinel_for<std::ranges::sentinel_t<_R>, std::ranges::iterator_t<_R>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

The corresponding concepts in the c++ standard are for exposition only. Should we move these concepts into an internal namespace then?


struct __uninitialized_move_fn
{
template<typename _ExecutionPolicy, oneapi::dpl::ranges::nothrow_random_access_range _InRange,
Copy link
Contributor

Choose a reason for hiding this comment

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

Only the output range is required to be nothrow according to the proposal.
The same applies to uninitialized_copy above.

{
template<typename _ExecutionPolicy, oneapi::dpl::ranges::nothrow_random_access_range _InRange,
oneapi::dpl::ranges::nothrow_random_access_range _OutRange>
requires std::constructible_from<std::ranges::range_value_t<_InRange>, std::ranges::range_reference_t<_OutRange>>
Copy link
Contributor

@dmitriy-sobolev dmitriy-sobolev Jun 16, 2025

Choose a reason for hiding this comment

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

It seems that the order is confused.

Suggested change
requires std::constructible_from<std::ranges::range_value_t<_InRange>, std::ranges::range_reference_t<_OutRange>>
requires std::constructible_from<std::ranges::range_value_t<_OutRange>, std::ranges::range_reference_t<_InRange>>

The same applies to uninitialized_move below.

Comment on lines +44 to +45
template<typename _S, typename _I>
concept nothrow_sentinel_for = std::sentinel_for<_S, _I>;
Copy link
Contributor

Choose a reason for hiding this comment

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

It defacto imposes no restrictions on exceptions, it's just an alias. What is the purpose of that? Should it be implemented this way?

Comment on lines +35 to +36
run_one_policy(alloc, oneapi::dpl::execution::seq, algo, checker, std::forward<decltype(args)>(args)...);
run_one_policy(alloc, oneapi::dpl::execution::unseq, algo, checker, std::forward<decltype(args)>(args)...);
Copy link
Contributor

Choose a reason for hiding this comment

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

args are forwarded multiple times.


#if _ENABLE_STD_RANGES_TESTING

#include "std_ranges_memory_test.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

There is no much sense to to have a separate header file - it is small, and only on cpp file uses it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants