【问题标题】:C++ async vs OpenMP tasksC++ 异步与 OpenMP 任务
【发布时间】:2017-12-19 20:55:35
【问题描述】:

在 OpenMP 中,我可以如下创建一堆任务并使用一些固定数量的线程异步运行它们:

#pragma omp parallel
{
   #pragma omp single 
   {
      for (int i = 0; i < 1000; i++) {
         #pragma omp task
         f(i);
}  }  }

在 C++11 中,我可以做一些事情不太一样 std::async:

std::vector<std::future> futures;
for (int i = 0; i < 1000; i++) {
   auto fut = std::async(f, i);
   futures.push_back(std::move(fut));
}
...
for (auto & fut : futures) {
  auto res = fut.get();
  // do something with res
}

我担心的是效率。如果我是正确的,在 OpenMP 中,任务存储在某个任务池中,然后分发到线程(由 OpenMP 运行时自动)。

在 C++ 中,在调用 std::async 时,运行时决定是在新线程中异步运行 f(i),还是将其运行推迟到调用 std::future::get 的时间点。

因此,无论是运行时

  1. 创建 1000 个线程并同时运行它们,
  2. 或创建更少的线程,但随后将在主线程中(在最终循环中)按顺序执行一些f(i) 调用。

这两个选项的效率似乎普遍低于 OpenMP 所做的(创建许多任务并在固定数量的线程中同时运行它们)。

有没有办法获得与 OpenMP 任务通过 C++ 线程提供的行为相同的行为?

更新

我使用以下代码进行了一些测量:https://wandbox.org/permlink/gLCFPr1IjTofxwQh 在 12C Xeon E5 CPU 上使用 GCC 7.2 和 -O2 编译:

  1. 具有 12 个线程的 OpenMP 运行时:12.2 [s]
  2. C++ 线程运行时:12.4 [s]

(多次运行的平均值)。它们看起来几乎是一样的。

但是,我也对 500,000 个任务 (n) 和其中的 1,000 次迭代 (m) 进行了相同的尝试,然后时间差异很大:

  1. 具有 12 个线程的 OpenMP 运行时:15.1 [s]
  2. C++ 线程处理运行时间:175.6 [s]

更新 2

我测量了创建了多少次新线程(按照这个答案插入 pthread_create 调用:https://stackoverflow.com/a/3709027/580083):

第一个实验(20,000 个任务,20,000 次迭代):

  1. 具有 12 个线程的 OpenMP 运行时:11
  2. C++ 线程处理运行时:20,000

第二次实验(500,000 个任务,1,000 次迭代):

  1. 具有 12 个线程的 OpenMP 运行时:11
  2. C++ 线程处理运行时:32,744

【问题讨论】:

  • 实现可以选择使用线程池实现std::async()。似乎 libstdc++ 使用了一个线程池(基于性能测量而不是查看代码)。
  • @DietmarKühl 你能分享你的绩效测量吗?看起来很有趣。
  • @Zulan:并不是特别令人兴奋:当查看我的Parallel Algorithms 演示文稿的数据时,尤其是使用std::threadstd::async() 手工制作的for_each,你可以看到它的性能类似于使用线程池的性能。在我上次测试行为时,其他实现的行为完全不同。
  • 如果您对测量 C++ 任务系统的性能感兴趣,那么您可能还想查看线程构建块 (TBB) threadingbuildingblocks.org(尽管 Intel 品牌它是 Apache 许可并在许多不同的架构(ARM、Power、SPARC、...)
  • @DietmarKühl 根据我的第二次更新,libstdc++ 似乎没有使用线程池。我认为这甚至不可能,请参阅祖兰的回答。实现可以延迟调用策略选择,但它仍然需要创建新线程或同步运行任务。

标签: c++ c++11 asynchronous task openmp


【解决方案1】:

你的分析不错,不过我觉得std::async里面的threadpools有漏洞。

OpenMP 确实使用固定的、用户控制的线程数量,这些线程非常灵活地执行任务。 untied 任务甚至可以在线程之间移动,尽管 doesn't seem to be well-supported in practice

是的,根据 C++11 标准,实现必须选择std::launch::asyncstd::launch::deferred。前者must create a std::thread object,而后者将在调用wait的线程中执行任务的代码。但是,该标准留下了一个注释(强调我的):

如果此策略与其他策略一起指定,例如当使用 policylaunch::async | launch::deferred 时,实现应该推迟调用或策略的选择当不能再并发时有效利用。

老实说,没有看到除了该注释之外的标准措辞如何允许实现推迟决定 - 但该标准似乎实际上鼓励线程池!如果选择launch:async 的决定被推迟,这意味着所需的新std::thread 可以重用现有的执行线程——至少我不明白为什么不这样做。

最初我认为std::thread 也可以实现为绿色线程,这也意味着线程池。但是,线程 [由&lt;thread&gt; 管理] 的标准注释旨在与操作系统线程进行一对一的映射。

在一天结束时衡量以验证您的表现。可能存在非常糟糕的 OpenMP 实现或非常聪明的标准库实现。

This answer to a similar question,提供了一些测量结果表明std::async 的开销很高,并且还共享了测量代码。

【讨论】:

【解决方案2】:

这两个选项的效率似乎普遍低于 OpenMP 所做的(创建许多任务并在固定数量的线程中同时运行它们)。

问题是,您实际上并不知道 OpenMP 正在做什么。 OpenMP 是一种 API,可让您指定“可以并行化此任务”并提供有关实现应如何尝试并行化它的指南。除非您自己指定它们,否则它不会公开该实现的低级细节。它可能会尝试分配固定数量的线程来在每个线程中执行批量任务,或者它可能会为每个单独的任务创建线程。如果实现认为您的目标机器无法从并行性中受益,它甚至可能完全放弃分配任何线程。

这既是 OpenMP 的优点也是缺点:它会尝试做它认为最适合您的特定任务的事情,这可能意味着优化程序的运行时,但它也可能偶尔会错误地判断实际上什么是最好的. std::async 在大多数方面都在同一条船上:实现(和编译器)将努力尝试猜测代码中最有效的字节码是什么,但总有可能出错。所以std::async 也经常被实现为使用专用(固定大小)线程池。它可能会将每个任务拆分到自己的线程中,或者在单个线程上执行所有操作。

如果您对多线程代码的精确实现有期望,则需要使用较低级别的概念和对象(如 std::thread)来指定这些期望。如果你想让你的编译器假设什么是最好的(而且编译器通常是正确的),std::async 和 OpenMP 可能是可比的。

当然,衡量也很重要:您的情况可能是 OpenMP 最了解什么是最快的情况。或者可能不是。您确定的唯一方法是您是否同时实现这两个版本并衡量哪个版本最快。

【讨论】:

  • 我基本同意,尤其是测量的必要性。但是,对于“它可能会尝试分配固定数量的线程来在每个线程中执行批量任务,或者它可能会为每个单独的任务创建线程。”,我认为这不是正确的。线程数是为 parallel 部分指定的(例如,由 OMP_NUM_THREADS 环境变量从外部指定),因此是固定的。
  • @DanielLangr 是的,但您的代码中没有任何部分指定 OMP_NUM_THREADS 的值。所以你不知道这个值对于你的实现实际上是什么。
  • 这个值可以由用户指定,它是环境变量,不是C++变量。或者线程数通常默认设置为 CPU 内核/硬件线程数。我的观点是 OpenMP 中的线程数是固定的,可以由用户控制。
  • @Daniel 几乎是对的,但如果您阅读 OpenMP 标准 (openmp.org/wp-content/uploads/openmp-4.5.pdf),您会发现算法 2.1 说明了 OpenMP 运行时允许使用多少线程。在许多情况下,即使您确实提出了明确的请求,答案也会以“实现定义”的形式出现。
  • @JimCownie 在很多情况下?我在那里只看到 1 个案例,仅在 ThreadsRequested > ThreadsAvailable 时适用。例如,在我的 Linux 服务器上,ThreadsAvailable 是 2147483647,因此我可以合理地期望我可以真正控制线程数,例如,通过 num_threads 子句表达式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-01
  • 2017-12-06
  • 2018-07-03
  • 1970-01-01
  • 2016-11-23
相关资源
最近更新 更多