Поскольку в C++ 17 нет индекса параллельно для алгоритма, мне интересно, можно ли использовать ranges::view::iota в сочетании с std::for_each для имитации этого. Это:
using namespace std;
constexpr int N= 10'000'000;
ranges::iota_view indices(0,N);
vector<int> v(N);
for_each(execution::par_unseq,indices.begin(),indices.end(),[&](int i) { v[i]= i; });
iota_view, похоже, предоставляет произвольный доступ для соответствующих типов ([range.iota.iterator]):
iota_view<I, Bound>::iterator::iterator_categoryis defined as follows:(1.1) — If
ImodelsAdvanceable, theniterator_categoryisrandom_access_iterator_tag.(1.2) — Otherwise, if
ImodelsDecrementable, theniterator_categoryisbidirectional_iterator_tag.(1.3) — Otherwise, if
ImodelsIncrementable, theniterator_categoryisforward_iterator_tag.(1.4) — Otherwise,
iterator_categoryisinput_iterator_tag.
Правильный ли приведенный выше код? Есть ли снижение производительности при использовании iota_view таким образом?
Обновлено: Я провел несколько тестов с диапазон-v3, cmcstl2 и Intel PSTL.
При использовании range-v3 приведенный выше пример не может быть скомпилирован с GCC 8. Компилятор жалуется на разные типы begin и end:
deduced conflicting types for parameter ‘_ForwardIterator’ (‘ranges::v3::basic_iterator<ranges::v3::iota_view<int, int> >’ and ‘ranges::v3::default_sentinel’)
При использовании cmcstl2 код компилируется чисто, но не выполняется параллельно. Мне кажется, что он возвращается к последовательной версии, может быть, потому, что требования к прямым итераторам как-то не выполняются (https://godbolt.org/z/yvr-M2).
Есть несколько связанная проблема с PSTL (https://github.com/intel/parallelstl/issues/22).
@Caleth Я хочу имитировать своего рода tbb::parallel_for, а не только тот конкретный пример, который я опубликовал. Я надеюсь, что смогу написать ranges::for_each(execution::par_unseq,view::iota(0,N),[&](int i) { /* ... */}).
Хорошо, давайте перефразируем: почему бы не использовать соответствующий алгоритм, а не всегда переписывать его в терминах for_each?
@Caleth Иногда трудно переформулировать цикл for в терминах stl-алгоритма. Например, когда задействовано несколько векторов и т. д.
Вы можете ranges::combine несколько векторов вместе, которые используют один индекс
Кроме того, если вы столкнетесь с определенными трудностями при поиске подходящего алгоритма, возможно, то, что вы делаете, нельзя произвольно распараллелить.
@Caleth Я только говорю, что иногда бывает полезно иметь основанный на индексе параллельный цикл for. Например, у меня есть мотивирующий пример использования, состоящий в вычислении параллельной неполной LU-факторизации безматричной линейной системы. В этом случае для меня гораздо понятнее использовать индексы, чтобы понять, что я делаю. Я не предлагаю всегда заменять выделенные алгоритмы циклами for.
Это не представление диапазонов, но я смог создать итератор, подобный йоте, который работает для параллельного выполнения (par, а не par_unseq). Код доступен в stackoverflow.com/questions/57638255/…
@Caleth К сожалению, ranges::views::zip страдает той же проблемой, что и ranges::view::iota. Он не моделирует Cpp17ForwardIterator, и поэтому его использование в параллельных алгоритмах несовместимо.
std::ranges::common_view предоставляет перегрузки std::ranges::begin и std::ranges::end, которые затем можно использовать в алгоритмах STL, даже с модификатором std::execution::par. Пожалуйста, посмотрите мой ответ ниже для образца.





Покопавшись в стандартном черновике, я боюсь, что ответ отрицательный: это не совсем соответствует стандарту для использования
ranges::iota_view в параллельной версии for_each.
Параллельная перегрузка for_each объявлена как [alg.foreach]:
template<class ExecutionPolicy, class ForwardIterator, class Function> void for_each(ExecutionPolicy&& exec, ForwardIterator first, ForwardIterator last, Function f);
С другой стороны, в [алгоритмы.требования] мы находим ограничение:
If an algorithm's template parameter is named
ForwardIterator,ForwardIterator1, orForwardIterator2, the template argument shall satisfy the Cpp17ForwardIterator requirements.
Как отметил Билли О'Нил в одной из ссылок, которые я разместил в вопросе, разумная реализация ranges::iota_view::iterator вряд ли будет соответствовать требованию прямого итератора [iterator.cpp17] «равные итераторы ссылаются на один и тот же объект». Следовательно, в моем понимании, ranges::iota_view::iterator не будет удовлетворять требованиям Cpp17ForwardIterator, и то же самое, например,
boost::counting_iterator.
Однако на практике я ожидал, что реализации будут использовать std::iterator_traits::iterator_category для отправки
соответствующая перегрузка алгоритма, как, кажется, делает PSTL. Поэтому я считаю, что пример кода в OP будет работать так, как задумано. Причина того, что cmcstl2 не работает, вероятно, состоит в том, что используемый iterator_category принадлежит к __stl2 пространство имен, а не к std.
std::ranges::iota_view<long, long>::iterator - это даже не итератор ввода: gcc.gnu.org/bugzilla/show_bug.cgi?id=93651.
В C++ 20 есть std::views::common, который адаптирует диапазон к стандартным алгоритмам приема пары итераторов. После преобразования входного диапазона в std::ranges::common_range используйте функции std::ranges::begin и std::ranges::end, чтобы получить пару итераторов для std::transform или любого другого алгоритма, который вы используете.
Вот пример программы, предполагающей компилятор C++ 20 (это нет, реализация на основе ranges-v3). Единственный, который я тестировал (по состоянию на октябрь 2020 года), - это G ++ версии 10.
#include <algorithm>
#include <numeric>
#include <execution>
#include <iostream>
#include <vector>
#include <ranges>
int main()
{
// A "large" number of elements (limited to ten for a reasonably small std::cout output)
constexpr int N = 10;
// Some range with a finite number of values (views::take at the end)
auto very_long_input_range = std::views::iota(0) | std::views::take(N);
// Source range converted to common_range (which supports std::begin & std::end)
auto input_range = std::ranges::common_view(very_long_input_range);
// Element processing function. E.g., if 'i' is a file name and this lambda parses it, it might be a big time-saver
auto some_complex_function = [](auto i){ return i * i; };
// Declare and allocate an output array (maybe range_value_t is an overkill here, but still)
// Using std::ranges::size(input_range) instead of N can also help generalize this code,
// but input_range must satisfy the std::ranges::sized_range concept
std::vector< std::ranges::range_value_t<decltype(input_range)> > output_array( N );
// Use C++17 std::execution::par with a pair of C++20 iterators from std::ranges
std::transform(std::execution::par,
std::ranges::begin(input_range),
std::ranges::end(input_range),
output_array.begin(),
some_complex_function);
// Test the output
for (auto p: output_array)
std::cout << p << std::endl;
}
Командная строка для G ++ 10 (Ubuntu 20.20):
g++-10 -std=c++2a -o ptest ptest.cpp -ltbb -lstdc++
Проблема не только в том, что begin и end могут иметь разные типы, но и в том, что итераторы этих адаптеров диапазона не требуются для моделирования Cpp17ForwardIterator (см. gcc.gnu.org/bugzilla/show_bug.cgi?id=93651). Например, это не работает параллельно: godbolt.org/z/nPfexe, тогда как это выполняется параллельно: godbolt.org/z/1oEo3c.
@metalfox: Я согласен, есть много очевидных проблем с текущей библиотекой Ranges, начиная с того факта, что большинство примеров даны для Ranges-v3, а не стандартной библиотеки, а стандартная библиотека реализована только в G ++ 10, где вариантов просмотра меньше, чем в Ranges-v3. Этот ответ вдохновлен моим ограниченным знакомством с диапазонами и волнением, что хотя бы вариант кода некоторый может работать параллельно. Лучшим вариантом использования для меня сейчас является создание двухмерной комбинации йот (временной сетки) и запуск некоторого трассировщика лучей для этого диапазона - это должно работать параллельно.
Мне нравится библиотека диапазонов. Я бы сказал, что проблема в старых компонентах, которые не были приспособлены для хорошей игры с диапазонами. Из того, что я тестировал до сих пор, итераторы iota_view<long, long> не распознаются libstd ++ как произвольный доступ на x86_64, потому что его тип различия имеет ширину 128 бит. iota_view<int, int> явно отлично работает с параллельными алгоритмами.
Подведем итог этому обсуждению, заявив, что «диапазоны находятся в стандарте C++ 20 в незавершенном состоянии, но это сделано для того, чтобы в будущих версиях стандарта люди думали, как сделать старые контейнеры / алгоритмы / exec_policies совместимыми с диапазоны, а не включение диапазонов ")
почему не
copy, а неfor_each?