C ++异步与OpenMP任务

时间:2017-12-19 20:55:35

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

在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)还是将其运行推迟到调用{ {1}}。

因此,要么是运行时

  1. 创建1000个线程并同时运行它们,
  2. 或创建更少的线程,但随后将在主线程中(在最终循环内)按顺序执行std::future::get的某些调用。
  3. 这些选项似乎通常效率低于OpenMP(创建许多任务并在固定数量的线程中同时运行)。

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

    更新

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

    1. 具有12个线程的OpenMP运行时:12.2 [s]
    2. C ++线程运行时:12.4 [s]
    3. (几次运行的平均值)。它们似乎实际上是一样的。

      但是,我也尝试过相同的500,000个任务(-O2)和1000个迭代(n),然后时间差别很大:

      1. 具有12个线程的OpenMP运行时:15.1 [s]
      2. C ++ threding runtime:175.6 [s]
      3. 更新2

        我测量创建新主题的次数(按照此m调用的回答:https://stackoverflow.com/a/3709027/580083):

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

        1. 具有12个线程的OpenMP运行时: 11
        2. C ++ threding runtime: 20,000
        3. 第二个实验(500,000个任务,1000个迭代):

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

2 个答案:

答案 0 :(得分:3)

您的分析非常好,但我认为std::async中的线程池存在漏洞。

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的线程中执行任务代码。但是,标准留下了一个注释(强调我的):

  

如果此策略与其他策略一起指定,例如使用policy launch::async | launch::deferred值时,实现应该在没有时推迟调用或选择策略可以有效地利用更多的并发性。

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

最初我认为std::thread也可以实现为绿色线程,其排序也意味着线程池。但是,线程[由<thread>]管理的标准备注旨在与操作系统线程一对一映射。

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

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

答案 1 :(得分:0)

  

这些选项似乎通常效率低于OpenMP(创建许多任务并在固定数量的线程中同时运行)。

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

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

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

当然,衡量一下非常重要:您的情况可能是OpenMP最了解什么是最快的情况。或者可能不是。您可以肯定的唯一方法是,如果您实施两个版本并测量哪个版本最快。