Как использовать параллельный std::for_each в системах, где <execution> отсутствует?

Этот фрагмент C++17 отлично работает в новых версиях G++.

std::for_each(std::execution::par_unseq, container.begin(), container.end(), [&](const auto& element) {
    // do something with element... or don't. whatever.
});

Когда вы пытаетесь перенести этот код, например, в текущий дистрибутив Debian (стабильный), который имеет G++ 8.3.0 (по состоянию на 12/2020), вы обнаружите, что читаете следующее сообщение об ошибке:

fatal error: execution: File not found
     #include <execution>
              ^~~~~~~~~~~

Очевидное решение, основанное на __has_include, которое включает макрос:

#if __has_include(<execution>)
    #include <execution>
    #define PAR_UNSEQ std::execution::par_unseq,
#else
    #define PAR_UNSEQ
#endif

std::for_each(PAR_UNSEQ container.begin(), container.end(), [&](const auto& element) {
    // do something with element... or don't. whatever.
});

Это компилируется нормально, но, на мой взгляд, имеет 2 основные проблемы:

  • Это не параллель для систем, в которых нет заголовка <execution> и
  • этот макрос не то, на что мне нравится смотреть.

Так есть ли лучший способ?

Или, если нет, есть ли хотя бы макрос-решение, которое фактически выполняет параллель for_each?

В случае for_each() вместо этого вы можете использовать параллельный цикл for OpenMP.

Shawn 21.12.2020 01:40

@Shawn Никогда не пробовал OpenMP, так как STL уже имеет эту функциональность, по крайней мере, я так думал. Есть ли у этого какие-либо плюсы/минусы по сравнению с другим ответом?

nada 21.12.2020 12:14

Он широко поддерживается компиляторами и довольно переносим, ​​в отличие от параллельного C++17 for_each() (я подозреваю), по крайней мере, еще на 5 лет, если не дольше.

Shawn 21.12.2020 12:53
Почему в Python есть оператор &quot;pass&quot;?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
3
3
1 426
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

<execution> был добавлен в gcc 9 (я думаю, 9.1), но вместо этого вы можете использовать базовую библиотеку, Intel® oneAPI Threading Building Blocks или tbb для краткости. Это немного громоздко, но что-то вроде приведенного ниже будет работать в gcc 8.3, если вы связываетесь с -ltbb (с которым вам нужно связываться даже в более новых версиях gcc, где <execution> включен). В моем примере используется tbb::parallel_for, который, скорее всего, также использует gcc:s std::for_each.

Он решает, как я считаю, самую важную часть, а именно то, что он работает параллельно в системах, у которых нет заголовка <execution>.

#if __has_include(<execution>)
    #include <execution>
#else
    #include "tbb/tbb.h"

    // define a support class for using with tbb::paralell_for
    template<typename C, typename Func>
    class ApplyFunc {
    public:
        ApplyFunc(const C& container, Func func) : c(container), f(func) {}

        // the function that will be called for each block
        void operator()(const tbb::blocked_range<size_t>& r) const {
            for(size_t i = r.begin(); i != r.end(); ++i) 
                f(c[i]); // the function you'd like to apply to each element
        }

    private:
        const C& c;
        Func f;
    };
#endif

// A function to call std::for_each or the tbb version
template<typename C, typename Func>
decltype(auto) myfor_each(const C& container, Func func) {
    #if __has_include(<execution>)
        std::for_each(std::execution::par, container.begin(), container.end(), func);
    #else
        tbb::parallel_for(tbb::blocked_range<size_t>(0, container.size()),
                          ApplyFunc(container, func));
    #endif
}

int main() {
    // example usage
    std::vector<int> container;
    myfor_each(container, [](auto& e) { do something eith e });
}

Это действительно очень неуклюже :/ Дополнительные ссылки на tbb и прочее, а также if / else для каждого for_each ...

nada 20.12.2020 23:54

@nada Я согласен, но это способ использовать tbb без <execution>. Вам нужно связать tbb, чтобы использовать <exucution> даже в более новых версиях g++, так что ничего лишнего. См. обновленный код для помещения вызова любой версии в функцию.

Ted Lyngmo 21.12.2020 00:26

Извините, что продолжаю ворчать, но вы уверены насчет ссылки на tbb? Я не связываю сборку Debian иначе, чем ту, которая имеет <execution>...

nada 21.12.2020 12:13

@nada В этом случае версия gcc для Debian неявно связана с tbb. Так или иначе, это tbb используется. Делай ldd your_binary | grep libtbb и увидишь.

Ted Lyngmo 21.12.2020 12:36

Сборка, в которой работает <execution>, на самом деле представляет собой G++ 10 баллов от mingw64. И я не вижу ничего tbb, когда печатаю ldd в своем двоичном файле, слишком много других, чтобы публиковать здесь в комментариях. libwinpthread-1.dll похоже, что-то связанное с тредами ^^. Может ли это быть?

nada 21.12.2020 17:07

@nada Ах ... это может быть библиотека потоков Posix pthread, объединенная вместе с tbb, или tbb статически связана (и не будет отображаться с ldd).

Ted Lyngmo 21.12.2020 17:11

@nada Если у вас не установлен libtbb, libstdc++ автоматически возвращается к последовательной версии алгоритма. Если ваша программа успешно компонуется без -ltbb, я боюсь, что она не будет работать параллельно. Ну, по крайней мере, когда речь идет о GCC10, но это поведение изменилось для 11: см. gcc.gnu.org/bugzilla/show_bug.cgi?id=97549.

metalfox 22.12.2020 16:39

@nada Это не имеет смысла. В настоящее время pstl от Intel поддерживает только серверную часть TBB и последовательную серверную часть. Итак, если ldd your_executable | grep tbb ничего не возвращает, он просто не может работать параллельно. В моей системе Linux, где у меня есть доступ к libtbb через мой LIBRARY_PATH, если я не связываюсь с -ltbb, я получаю неопределенные символы TBB. Напротив, у меня есть старый ноутбук с Cygwin, где нет libtbb. Я могу использовать par_unseq без привязки TBB, но это не работает параллельно.

metalfox 23.12.2020 09:56

@nada Как я уже сказал, в настоящее время параллельным алгоритмам libstdC++ требуется TBB для параллельной работы. В настоящее время разрабатываются альтернативные параллельные серверные части. Вы можете найти больше информации в этой теме: lists.llvm.org/pipermail/libcxx-dev/2020-September/000935.ht‌​ml.

metalfox 23.12.2020 09:56

@metalfox Ты был прав. Мой тест был фальшивым, потому что я забыл перезагрузить CMake (и фактически связать tbb, сделав это) в какой-то момент. Tbb должен быть слинкован, чтобы <execution> работал параллельно. Извините моя ошибка.

nada 24.12.2020 10:46

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