Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't std::execution::par launch threads with std::views::iota iterators

Here are two version of the code. One uses std::vector iterators in the std::for_each call, the other uses the std::views::iota iterators.

I expect both of these to use multiple threads, since I'm using std::execution::par, but it appears like that isn't the case. The iota version only uses a single thread and I don't understand why.

Vector version

#include <algorithm>
#include <execution>
#include <iostream>
#include <mutex>
#include <ranges>
#include <thread>
#include <vector>

int main()
{
   std::mutex io_mutex;
   auto offsets0_ = std::views::iota(0, 8);
   auto offsets0 = std::vector<int>(std::begin(offsets0_), std::end(offsets0_));
   std::for_each(
      std::execution::par,
      std::begin(offsets0), std::end(offsets0),
      [&](auto offset0) {
         io_mutex.lock();
         std::cout << std::this_thread::get_id() << ": " << offset0 << '\n';

         // Emulate doing a bunch of work, otherwise threads don't spawn in either scenario
         std::this_thread::sleep_for(std::chrono::milliseconds(200));
         io_mutex.unlock();
      });
}

Output

139803706330688: 0
139803706330688: 1
139803695208000: 6
139803691009600: 5
139803699406400: 4
139803706330688: 2
139803695208000: 7
139803691009600: 3

So it used 4 threads to go through all items. My PC has 4 cores, so that makes sense.

Iota ranges version

#include <algorithm>
#include <execution>
#include <iostream>
#include <mutex>
#include <ranges>
#include <thread>
#include <vector>

int main()
{
   std::mutex io_mutex;
   auto offsets0 = std::views::iota(0, 8);
   std::for_each(
      std::execution::par,
      std::begin(offsets0), std::end(offsets0),
      [&](auto offset0) {
         io_mutex.lock();
         std::cout << std::this_thread::get_id() << ": " << offset0 << '\n';

         // Emulate doing a bunch of work, otherwise threads don't spawn in either scenario
         std::this_thread::sleep_for(std::chrono::milliseconds(200));
         io_mutex.unlock();
      });
}

output

139954983456320: 0
139954983456320: 1
139954983456320: 2
139954983456320: 3
139954983456320: 4
139954983456320: 5
139954983456320: 6
139954983456320: 7

With the iota ranges iterators it only a single thread.. Why? are ranges not supposed to be used like this?

Both of these were compiled with these options: g++ main.cpp -std=c++20 -ltbb -O3. Using gcc 11.1.0, glibc 2.33, and tbb 2020.3.

like image 726
Dexter CD Avatar asked May 12 '26 20:05

Dexter CD


1 Answers

As per cppreference, this is the signature of for_each with the execution policy parameter.

template< class ExecutionPolicy, class ForwardIt, class UnaryFunction2 >
void for_each( ExecutionPolicy&& policy, ForwardIt first, ForwardIt last, UnaryFunction2 f );

Where ForwardIt should satisfy the LegacyForwardIterator requirements.

But iota_view::iterator only goes up to LegacyInputIterator when the value_type is 'incrementable', from my understanding it never satisfies LegacyForwardIterator. cppreference

This is probably why the for_each call doesn't do what you'd expect.

I'm not sure why it's not just a compilation error, maybe it's intentional, though it does feel unusual to completely ignore the execution policy. I hope another answer can shed light on this.

like image 121
Dexter CD Avatar answered May 15 '26 11:05

Dexter CD



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!