oneTBB icon indicating copy to clipboard operation
oneTBB copied to clipboard

Unexpected result with read/write zip_view and parallel_for_each

Open rubenvb opened this issue 10 months ago • 1 comments

Summary

parallel_for_each gives a different result than std::ranges::for_each when passing a read/write std::ranges::zip_view.

Version

2021.4, 2021.10

Environment

  • Hardware: x86_64 and aarch64
  • OS name and version: LInux, macOS 15
  • Compiler version: Xcode 16.1 Clang and GCC 14.1

Observed Behavior

parallel_for_each "loses" the writable reference to elements in a zip_view, in contrast to std::ranges::for_each, as demonstrated in this program:

#include <ranges>
#include <vector>
#include <iostream>
#include <algorithm>

#include <tbb/parallel_for_each.h>

int main()
{
    const std::vector<int> ints{1,2,3};
    const std::vector<double> doubles{0.1, 0.2, 0.3};

    const auto multiply = [](auto&& intDoubleProduct)
    {
        auto&& [i, d, product] = intDoubleProduct;
        product = i * d;
    };

    {
        std::vector<double> product = {0, 0, 0};
        std::ranges::for_each(std::views::zip(ints, doubles, product), multiply);
        std::cout << "std::ranges::for_each result: (";
        for(auto value : product)
            std::cout << value << ", ";
        std::cout << '\n';
    }
    {
        std::vector<double> product = {0, 0, 0};
        tbb::parallel_for_each(std::views::zip(ints, doubles, product), multiply);
        std::cout << "tbb::parallel_for_each result: (";
        for(auto value : product)
            std::cout << value << ", ";
        std::cout << '\n';
    }
}

https://godbolt.org/z/EvMWGsTP5

Expected Behavior

parallel_for_each writes to the elements in the zipped vector and matches the outcome of std::ranges::for_each. TBB should be "smarter" about the range it's passed, the core issue seems to be what is described in this StackOverflow answer: https://stackoverflow.com/a/79451943/256138

Note the above example is simple, and the zip_view is passed directly to tbb::parallel_for_each. This case, as described in the comments to that SO answer, can be rewritten to function correctly with a proper wrapper. Unfortunately that inhibits further "range" operations such as std::views::filter, e.g.

const auto is_even = [](const auto& intDoubleProduct) { return std::get<0>(intDoubleProduct) % 2 == 0; };
tbb::parallel_for_each(std::views::zip(ints, doubles, product) | std::views::filter(is_even), multiply);

Which may also change the range/view type that TBB sees in some way (although piping through std::views::all did nothing beneficial to the original issue.

Steps To Reproduce

See code above.

rubenvb avatar Feb 20 '25 08:02 rubenvb

@rubenvb,

Prior to oneTBB 2021.12, parallel_for_each contained an issue while working with C++20 iterators (including iterators of ranges). It was fixed by https://github.com/uxlfoundation/oneTBB/commit/de0a3a653ba80c1d324e49a1b1faa01fc459d02d Consider updating the TBB version you use to at least oneTBB 2021.12 to get the fix. I have tried locally and it fixed the code snippet you provided.

kboyarinov avatar Feb 20 '25 13:02 kboyarinov