Skip to content

Conversation

danhoeflinger
Copy link
Contributor

@danhoeflinger danhoeflinger commented Jul 25, 2025

This PR adds support within [transform_]reduce for types with only move constructor and move assignment operator (no copy assignment or construction) for host backends (tbb and omp).

It adds host policy tests for move only types within [transform_]reduce.pass.

This is part of the resolution for #1955.

@danhoeflinger danhoeflinger force-pushed the dev/dhoeflin/host_reduce_move_only branch 2 times, most recently from a8d5077 to d6d3860 Compare July 28, 2025 14:05
Base automatically changed from dev/dhoeflin/remove_default_constructible_req to main July 29, 2025 12:51
@danhoeflinger danhoeflinger added this to the 2022.10.0 milestone Aug 4, 2025
@danhoeflinger danhoeflinger force-pushed the dev/dhoeflin/host_reduce_move_only branch from 5579d0f to 9da7b42 Compare August 13, 2025 15:46
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for move-only types (types with only move constructor and move assignment operator) to the reduce and transform_reduce algorithms for host backends (TBB and OpenMP). The implementation ensures proper move semantics are used throughout the algorithm chains to avoid copy operations.

  • Introduces MoveOnlyWrapper test utility class that enforces move-only semantics
  • Updates algorithm implementations to use std::move() for init values and intermediate results
  • Adds host-policy specific tests for move-only types in reduce and transform_reduce

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/support/utils.h Adds MoveOnlyWrapper template class for testing move-only type semantics
test/parallel_api/numeric/numeric.ops/transform_reduce.pass.cpp Updates test functions to support move semantics and adds move-only type tests
test/parallel_api/numeric/numeric.ops/reduce.pass.cpp Adds move-only type tests for reduce algorithm
include/oneapi/dpl/pstl/utils.h Updates comment formatting for __lazy_ctor_storage destructor
include/oneapi/dpl/pstl/parallel_backend_tbb.h Refactors TBB backend to use __lazy_ctor_storage and proper move semantics
include/oneapi/dpl/pstl/parallel_backend_serial.h Updates serial backend to use move semantics
include/oneapi/dpl/pstl/omp/parallel_transform_reduce.h Updates OpenMP backend to use move semantics throughout
include/oneapi/dpl/pstl/numeric_impl.h Updates brick functions and patterns to use move semantics
include/oneapi/dpl/pstl/glue_numeric_impl.h Updates public API functions to use move semantics

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@SergeyKopienko
Copy link
Contributor

SergeyKopienko commented Aug 14, 2025

Let me ask some overall questions about this change:

  1. We still have a lot of code like , __init, in some functions calls. I think some of these places may bring for as the same problem as you want to fix in this PR. How do you think?
    Example:
template <class _ExecutionPolicy, class _ForwardIterator, class _Tp, class _BinaryOperation, class... _Events,
          oneapi::dpl::__internal::__enable_if_device_execution_policy_double_no_default<_ExecutionPolicy, int, _Tp,
                                                                                         _BinaryOperation, _Events...>>
auto
reduce_async(_ExecutionPolicy&& __exec, _ForwardIterator __first, _ForwardIterator __last, _Tp __init,
             _BinaryOperation __binary_op, _Events&&... __dependencies)
{
    const auto __dispatch_tag = oneapi::dpl::__internal::__select_backend(__exec, __first);

    wait_for_all(std::forward<_Events>(__dependencies)...);
    auto ret_val = oneapi::dpl::__internal::__pattern_transform_reduce_async(
        __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, __init, __binary_op,
        oneapi::dpl::identity{});
    return ret_val;
}
  1. What it on all levels of code excepting our external interface level we rewrite _Tp __init as _Tp&& __init and then will not pass it or move it into the calls but forward it?

@timmiesmith timmiesmith linked an issue Aug 18, 2025 that may be closed by this pull request
Copy link
Contributor

@mmichel11 mmichel11 left a comment

Choose a reason for hiding this comment

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

Minor questions from me.

MoveOnlyWrapper(MoveOnlyWrapper&&) = default;

// Move assignment operator
MoveOnlyWrapper& operator=(MoveOnlyWrapper&&) = default;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add a documentation update of our decision to require move assignment somewhere with the justification of why this is required to implement parallel reduce?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I can add to the documentation in 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.

I've augmented the documentation. I did not go into detail about why this is necessary as I do not really see a good location to have this discussion. It is not specific to parallel reduce, it even affects sequential reduce implementations. There is a way to implement it with only move construction, but it requires recursive handling which creates a stack overflow if the size is too large.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@akukanov, Would it be reasonable to link to https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0571r2.html from our documentation?

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 added the link for now.

@danhoeflinger
Copy link
Contributor Author

@SergeyKopienko Sorry I missed this feedback.

Let me ask some overall questions about this change:

  1. We still have a lot of code like , __init, in some functions calls. I think some of these places may bring for as the same problem as you want to fix in this PR. How do you think?
    Example:
template <class _ExecutionPolicy, class _ForwardIterator, class _Tp, class _BinaryOperation, class... _Events,
          oneapi::dpl::__internal::__enable_if_device_execution_policy_double_no_default<_ExecutionPolicy, int, _Tp,
                                                                                         _BinaryOperation, _Events...>>
auto
reduce_async(_ExecutionPolicy&& __exec, _ForwardIterator __first, _ForwardIterator __last, _Tp __init,
             _BinaryOperation __binary_op, _Events&&... __dependencies)
{
    const auto __dispatch_tag = oneapi::dpl::__internal::__select_backend(__exec, __first);

    wait_for_all(std::forward<_Events>(__dependencies)...);
    auto ret_val = oneapi::dpl::__internal::__pattern_transform_reduce_async(
        __dispatch_tag, std::forward<_ExecutionPolicy>(__exec), __first, __last, __init, __binary_op,
        oneapi::dpl::identity{});
    return ret_val;
}

What you refer to in the example is from the experimental async api. We can expand this change there, but I did not initially target this. Our testing should ensure that (at least for our testing coverage) [transform_]reduce works with move-only types fully. I can work on adding the other interfaces (async, ranges) which can lead to these backend algorithms. Are there other places beyond these that you've seen?

  1. What it on all levels of code excepting our external interface level we rewrite _Tp __init as _Tp&& __init and then will not pass it or move it into the calls but forward it?

Using forwarding references is kind of strange here. We know exactly that this type is coming in as value to the API, and that we must always move it rather than copy it. I guess its another possible approach, but I'm not sure what we gain from that really.

Signed-off-by: Dan Hoeflinger <[email protected]>
Signed-off-by: Dan Hoeflinger <[email protected]>
Signed-off-by: Dan Hoeflinger <[email protected]>
Signed-off-by: Dan Hoeflinger <[email protected]>
SergeyKopienko
SergeyKopienko previously approved these changes Aug 29, 2025
Copy link
Contributor

@SergeyKopienko SergeyKopienko left a comment

Choose a reason for hiding this comment

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

LGTM

Signed-off-by: Dan Hoeflinger <[email protected]>
Signed-off-by: Dan Hoeflinger <[email protected]>
Copy link
Contributor

@SergeyKopienko SergeyKopienko left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@SergeyKopienko SergeyKopienko left a comment

Choose a reason for hiding this comment

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

LGTM

@danhoeflinger danhoeflinger merged commit 0a54e67 into main Aug 29, 2025
19 checks passed
@danhoeflinger danhoeflinger deleted the dev/dhoeflin/host_reduce_move_only branch August 29, 2025 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Numeric PSTL algos require copy-constructible
3 participants