Использование range :: view :: iota в параллельных алгоритмах

Поскольку в нет индекса параллельно для алгоритма, мне интересно, можно ли использовать 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_category is defined as follows:

(1.1) — If I models Advanceable, then iterator_category is random_access_iterator_tag.

(1.2) — Otherwise, if I models Decrementable, then iterator_category is bidirectional_iterator_tag.

(1.3) — Otherwise, if I models Incrementable, then iterator_category is forward_iterator_tag.

(1.4) — Otherwise, iterator_category is input_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).

почему не copy, а не for_each?

Caleth 05.07.2018 10:41

@Caleth Я хочу имитировать своего рода tbb::parallel_for, а не только тот конкретный пример, который я опубликовал. Я надеюсь, что смогу написать ranges::for_each(execution::par_unseq,view::iota(0,N),[&](in‌​t i) { /* ... */}).

metalfox 05.07.2018 11:04

Хорошо, давайте перефразируем: почему бы не использовать соответствующий алгоритм, а не всегда переписывать его в терминах for_each?

Caleth 05.07.2018 11:09

@Caleth Иногда трудно переформулировать цикл for в терминах stl-алгоритма. Например, когда задействовано несколько векторов и т. д.

metalfox 05.07.2018 11:17

Вы можете ranges::combine несколько векторов вместе, которые используют один индекс

Caleth 05.07.2018 11:20

Кроме того, если вы столкнетесь с определенными трудностями при поиске подходящего алгоритма, возможно, то, что вы делаете, нельзя произвольно распараллелить.

Caleth 05.07.2018 11:21

@Caleth Я только говорю, что иногда бывает полезно иметь основанный на индексе параллельный цикл for. Например, у меня есть мотивирующий пример использования, состоящий в вычислении параллельной неполной LU-факторизации безматричной линейной системы. В этом случае для меня гораздо понятнее использовать индексы, чтобы понять, что я делаю. Я не предлагаю всегда заменять выделенные алгоритмы циклами for.

metalfox 06.07.2018 08:47

Это не представление диапазонов, но я смог создать итератор, подобный йоте, который работает для параллельного выполнения (par, а не par_unseq). Код доступен в stackoverflow.com/questions/57638255/…

Mr.WorshipMe 10.09.2019 21:25

@Caleth К сожалению, ranges::views::zip страдает той же проблемой, что и ranges::view::iota. Он не моделирует Cpp17ForwardIterator, и поэтому его использование в параллельных алгоритмах несовместимо.

Pilar Latiesa 14.02.2020 15:25

std::ranges::common_view предоставляет перегрузки std::ranges::begin и std::ranges::end, которые затем можно использовать в алгоритмах STL, даже с модификатором std::execution::par. Пожалуйста, посмотрите мой ответ ниже для образца.

Viktor Latypov 27.10.2020 12:23
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
10
4 124
2

Ответы 2

Покопавшись в стандартном черновике, я боюсь, что ответ отрицательный: это не совсем соответствует стандарту для использования 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, or ForwardIterator2, 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.
Pilar Latiesa 14.02.2020 15:18

В 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 27.10.2020 21:01

@metalfox: Я согласен, есть много очевидных проблем с текущей библиотекой Ranges, начиная с того факта, что большинство примеров даны для Ranges-v3, а не стандартной библиотеки, а стандартная библиотека реализована только в G ++ 10, где вариантов просмотра меньше, чем в Ranges-v3. Этот ответ вдохновлен моим ограниченным знакомством с диапазонами и волнением, что хотя бы вариант кода некоторый может работать параллельно. Лучшим вариантом использования для меня сейчас является создание двухмерной комбинации йот (временной сетки) и запуск некоторого трассировщика лучей для этого диапазона - это должно работать параллельно.

Viktor Latypov 28.10.2020 11:09

Мне нравится библиотека диапазонов. Я бы сказал, что проблема в старых компонентах, которые не были приспособлены для хорошей игры с диапазонами. Из того, что я тестировал до сих пор, итераторы iota_view<long, long> не распознаются libstd ++ как произвольный доступ на x86_64, потому что его тип различия имеет ширину 128 бит. iota_view<int, int> явно отлично работает с параллельными алгоритмами.

metalfox 28.10.2020 20:34

Подведем итог этому обсуждению, заявив, что «диапазоны находятся в стандарте C++ 20 в незавершенном состоянии, но это сделано для того, чтобы в будущих версиях стандарта люди думали, как сделать старые контейнеры / алгоритмы / exec_policies совместимыми с диапазоны, а не включение диапазонов ")

Viktor Latypov 28.10.2020 22:33

Другие вопросы по теме