【问题标题】:How to use a parallel std::for_each on systems where <execution> is missing?如何在缺少 <execution> 的系统上使用并行 std::for_each?
【发布时间】:2020-12-21 15:50:31
【问题描述】:

这个 C++17 sn-p 在新版本的 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(截至 2020 年 12 月))时,您会发现自己正在阅读以下错误消息:

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.
});

这编译得很好,但在我看来有两个主要问题:

  • 对于没有&lt;execution&gt; 标头和的系统而言,它不是并行的
  • 那个宏不是我喜欢看的东西。

那么有没有更好的办法呢?

或者如果没有,至少有一个宏解决方案实际上可以并行for_each

【问题讨论】:

  • for_each() 的情况下,您可以使用 OpenMP 并行 for 循环。
  • @Shawn 从未尝试过 OpenMP,因为 STL 已经具有该功能,至少我是这么认为的。与其他答案相比,这有什么优点/缺点吗?
  • 它受到编译器的广泛支持并且相当可移植,不像 C++17 并行 for_each() (我怀疑)至少再过 5 年,如果不是更长的话。

标签: c++ for-loop c++17


【解决方案1】:

&lt;execution&gt; 已添加到 gcc 9(我认为是 9.1)中,但您可以直接使用底层库 Intel® oneAPI Threading Building Blocks 或简称 tbb。这有点麻烦,但如果您与-ltbb 链接(即使在包含&lt;execution&gt; 的较新gcc 版本中也需要链接),类似下面的内容将在gcc 8.3 中工作。我的示例使用了tbb::parallel_for,这也是 gcc:s std::for_each 最有可能使用的。

它解决了我认为最重要的部分,那就是它在没有 &lt;execution&gt; 标头的系统上是并行的。

#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 我同意,但这是在没有&lt;execution&gt; 的情况下使用tbb 的一种方式。即使在较新的 g++ 版本中,您也需要与 tbb 链接才能使用 &lt;exucution&gt;,所以这没什么额外的。请参阅更新后的代码,将任一版本的调用放入函数中。
  • @nada 如果您没有安装 libtbb,libstdc++ 会默默地回退到算法的顺序版本。如果您的程序在没有-ltbb 的情况下成功链接,恐怕它不会并行运行。好吧,至少在 GCC10 方面,但这种行为已经改变了 11:请参阅 gcc.gnu.org/bugzilla/show_bug.cgi?id=97549
  • @nada 正如我所说,目前 libstdc++ 并行算法需要 TBB 才能并行运行。目前正在开发替代的并行后端。您可以在此线程中找到更多信息:lists.llvm.org/pipermail/libcxx-dev/2020-September/000935.html
  • @metalfox 你是对的。我的基准是虚假的,因为我在某些时候忘记重新加载 CMake(并实际上通过这样做链接 tbb)。必须链接 Tbb 才能使 并行工作。对不起,我的错。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-06
  • 2012-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-04
  • 1970-01-01
相关资源
最近更新 更多