Этот фрагмент 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?
@Shawn Никогда не пробовал OpenMP, так как STL уже имеет эту функциональность, по крайней мере, я так думал. Есть ли у этого какие-либо плюсы/минусы по сравнению с другим ответом?
Он широко поддерживается компиляторами и довольно переносим, в отличие от параллельного C++17 for_each() (я подозреваю), по крайней мере, еще на 5 лет, если не дольше.
<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 Я согласен, но это способ использовать tbb без <execution>. Вам нужно связать tbb, чтобы использовать <exucution> даже в более новых версиях g++, так что ничего лишнего. См. обновленный код для помещения вызова любой версии в функцию.
Извините, что продолжаю ворчать, но вы уверены насчет ссылки на tbb? Я не связываю сборку Debian иначе, чем ту, которая имеет <execution>...
@nada В этом случае версия gcc для Debian неявно связана с tbb. Так или иначе, это tbb используется. Делай ldd your_binary | grep libtbb и увидишь.
Сборка, в которой работает <execution>, на самом деле представляет собой G++ 10 баллов от mingw64. И я не вижу ничего tbb, когда печатаю ldd в своем двоичном файле, слишком много других, чтобы публиковать здесь в комментариях. libwinpthread-1.dll похоже, что-то связанное с тредами ^^. Может ли это быть?
@nada Ах ... это может быть библиотека потоков Posix pthread, объединенная вместе с tbb, или tbb статически связана (и не будет отображаться с ldd).
@nada Если у вас не установлен libtbb, libstdc++ автоматически возвращается к последовательной версии алгоритма. Если ваша программа успешно компонуется без -ltbb, я боюсь, что она не будет работать параллельно. Ну, по крайней мере, когда речь идет о GCC10, но это поведение изменилось для 11: см. gcc.gnu.org/bugzilla/show_bug.cgi?id=97549.
@nada Это не имеет смысла. В настоящее время pstl от Intel поддерживает только серверную часть TBB и последовательную серверную часть. Итак, если ldd your_executable | grep tbb ничего не возвращает, он просто не может работать параллельно. В моей системе Linux, где у меня есть доступ к libtbb через мой LIBRARY_PATH, если я не связываюсь с -ltbb, я получаю неопределенные символы TBB. Напротив, у меня есть старый ноутбук с Cygwin, где нет libtbb. Я могу использовать par_unseq без привязки TBB, но это не работает параллельно.
@nada Как я уже сказал, в настоящее время параллельным алгоритмам libstdC++ требуется TBB для параллельной работы. В настоящее время разрабатываются альтернативные параллельные серверные части. Вы можете найти больше информации в этой теме: lists.llvm.org/pipermail/libcxx-dev/2020-September/000935.html.
@metalfox Ты был прав. Мой тест был фальшивым, потому что я забыл перезагрузить CMake (и фактически связать tbb, сделав это) в какой-то момент. Tbb должен быть слинкован, чтобы <execution> работал параллельно. Извините моя ошибка.
В случае for_each() вместо этого вы можете использовать параллельный цикл for OpenMP.