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.
#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();
});
}
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.
#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();
});
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With