【问题标题】:How can I use all the cores in the loop?如何使用循环中的所有核心?
【发布时间】:2018-03-14 15:13:07
【问题描述】:

有一个循环。

for (int i = 0; i < n; ++i) {
    //...
    v[i] = o.f(i);
    //...
}

每个v[i] = o.f(i) 都独立于所有其他v[i] = o.f(i)
n 可以是任何值,并且不能是核心数的倍数。使用所有内核执行此操作的最简单方法是什么?

【问题讨论】:

  • 试试tbb库。
  • @Ufx:v[i] = o.f(i); 的开销是否足以值得将它们移植到其他内核然后同步结果?如果没有,请不要打扰。
  • 使用 c++11 std::async 是一种并行化作业的简单方法。使用 c++17,您可以提供运行 std::for_eachexecution policy
  • @Ufx 这些都是标准库的特性。如果“标准”是指 c++03,它并不比 c++11 和 c++17 更标准,只是语言的不同版本。当人们谈论标准 c++ 而不指定版本时,通常假定它是指最新发布的标准。
  • OpenMP 正是这样做的,大多数编译器(GCC、LLVM、MSVC)都支持它。通过在for 循环上添加类似#pragma omp parallel for 的行。

标签: c++ multithreading c++11 c++14 c++17


【解决方案1】:

&lt;algorithm&gt; 中算法的 ExecutionPolicy 重载就是为此目的而存在的。 std::transform 将函数应用于源范围的每个元素以分配给目标范围。

v.begin() 是可接受的目的地,只要v 的大小合适。您的 sn-p 在使用 v[i] 时会假设这一点,所以我也会这样做。

然后我们需要一个迭代器,它将值 [0, n) 作为我们的源,所以 boost::counting_iterator&lt;int&gt;

最后,我们需要一个 Callableo.f 应用于我们的值,所以让我们在 lambda 中捕获 o

#include <algorithm>
#include <execution>
#include <boost/iterator/counting_iterator.hpp>

// assert(v.size() >= n)
std::transform(std::execution::par, boost::counting_iterator<int>(0), boost::counting_iterator<int>(n), v.begin(), [&o](int i){ return o.f(i); });

如果o.f 不执行任何“向量化不安全操作”,您可以使用std::execution::par_unseq,这可能 在同一线程上交错调用(即展开循环并使用 SIMD说明)

【讨论】:

  • @FrançoisAndrieux 我不知道,它说“类似”。那不是代码。
  • 由于每个调用都是独立的,可能使用std::execution::par_unseq策略。
  • @user2079303 我刚刚添加了一些建议。
【解决方案2】:

在现有编译器领域,记住 M/S 甚至无法为 C++11 提供这些东西,更不用说 C++17/20,C++11 的答案类似于:

typedef v.value_type R;
std::vector< std::future<R> > fut(n);
for (int i=0; i<n; i++)
    fut[i] = std::async(std::launch::async, O::f, o, i);
for (auto& f : fut)
    v.push_back(f.get());

@arne 建议我们可以通过考虑处理器数量 (P) 来限制任务数量来做得更好,这是正确的,尽管上面的代码会清楚地表明你是否真的会从多线程中受益f. 方法鉴于我们只想同时启动 X 个作业,其中 X > P,

typedef v.value_type R;
std::vector< std::future<R> > fut(n);
for (ssize_t i=0, j=-X; j<n; i++,j++)
{
    if (i<n)    fut[i] = std::async(std::launch::async, O::f, o, i);
    if (j>=0)   v.push_back(fut[j].get());
}

我并不是说上面的代码“很棒”,但是如果工作足够复杂以至于我们需要多线程,那么额外循环几次的成本就不会被注意到。你会注意到如果 X > n 循环会在中间旋转几次,但会产生正确的结果:-)

【讨论】:

  • 这会为每个调用生成一个线程,这可能非常低效。我建议将工作分解为 n / std::this_thread::hardware_concurrency() 的块,如果后者不返回零(如果返回,则应使用合理的默认值)。
  • 是的,有很多方法可以让它更好地利用资源,但作为第一步,它会告诉你是否可以获得任何好处。如果您的作业大小不同,批处理和等待就会出现问题。驱动处理器负载平衡的另一种方法是启动前 X 个作业,然后在等待第一个已经启动的作业完成时一次启动一个作业,最后等待最后 X 个作业完成。
猜你喜欢
  • 2020-05-19
  • 2020-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多