std::packaged_task 真的很贵吗?

Posted

技术标签:

【中文标题】std::packaged_task 真的很贵吗?【英文标题】:is std::packaged_task really expensive? 【发布时间】:2013-10-12 17:49:47 【问题描述】:

我对以下代码在 Opensuse Linux 上使用 gcc 4.7.2 的结果感到惊讶:

#include <cmath>
#include <chrono>
#include <cstdlib>
#include <vector>
#include <chrono>
#include <iostream>
#include <future>

int main(void)

  const long N = 10*1000*1000;
  std::vector<double> array(N);
  for (auto& i : array)
    i = rand()/333.;

  std::chrono::time_point<std::chrono::system_clock> start, end;
  start = std::chrono::system_clock::now();
  for (auto& i : array)
    pow(i,i);
  end = std::chrono::system_clock::now();
  std::chrono::duration<double> elapsed_seconds = end-start;
  std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";

  start = std::chrono::system_clock::now();
  for (auto& i : array)
    std::packaged_task<double(double,double)> myTask(pow);
  elapsed_seconds = std::chrono::system_clock::now()-start;
  std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";

  start = std::chrono::system_clock::now();
  for (auto& i : array)
    std::packaged_task<double()> myTask(std::bind(pow,i,i));
  elapsed_seconds = std::chrono::system_clock::now()-start;
  std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";

  return 0;

结果看起来像这样(并且在运行中相当一致):

elapsed time: 0.694315s
elapsed time: 6.49907s
elapsed time: 8.42619s

如果我正确地解释结果,仅仅创建一个std::packaged_task(甚至还没有执行它或存储它的参数)已经比executingpow贵十倍。这是一个有效的结论吗?

为什么会这样?

这是 gcc 特有的吗?

【问题讨论】:

嗯,很自然,一个打包的任务包含昂贵的同步原语——总线锁和管道刷新是低级同步原语的常见结果,因此单线程同步总是会输给单线程同步。 -线程不同步。您实际上必须能够从并发或并行执行中受益,才能使并发解决方案成为可行的改进。 (您可以构建自己的打包任务with a promise。仅promise 就包含了一些严肃的同步机制。) @KerrekSB,如果创建 packaged_task 需要大量使用锁定原语,我会感到惊讶。毕竟,没有可能的争论(还)。创建这么多互斥体只需要 0.09 秒 - 所以除非你需要为每个 packaged_task 创建十个互斥体,否则还有很多空间...... 我会在您的库中研究std::promise 的实现。我没有看过自己,但我怀疑即使在初始化时也会做一些不平凡的事情。 在-O3,所有数字对我来说都大致相同。实际上,我很惊讶循环代码执行了任何操作。 【参考方案1】:

您没有为 packaged_task 的执行计时,只是它的创建。

std::packaged_task<double(double,double)> myTask(pow);

这不会执行myTask,只会创建它。理想情况下你不应该测量这个,你应该测量myTask(i, i),我通过将你的程序更改为以下来做到这一点(我用std::bind删除了测量)。

结果比你测量的要差:

timing raw
elapsed time: 0.578244s

timing ptask
elapsed time: 20.7379s

我猜packaged_tasks 不适合可重复的小任务,开销肯定大于任务本身。我对此的解读是,您应该将它们用于多任务代码,该任务所花费的时间比调用和同步 packaged_task 相关的开销要长。

如果您不是多任务处理,我认为将函数调用包装在准备好使用同步原语进行多线程处理的类中是没有意义的,遗憾的是,它们不是免费的。

为了记录,这是我使用的:

#include <cmath>
#include <chrono>
#include <cstdlib>
#include <vector>
#include <chrono>
#include <iostream>
#include <future>
#include <thread>

int main(void)

  const long N = 10*1000*1000;
  std::vector<double> array(N);
  for (auto& i : array)
    i = rand()/333.;

  std::cout << "timing raw" << std::endl;
  std::chrono::time_point<std::chrono::system_clock> start, end;
  start = std::chrono::system_clock::now();
  for (auto& i : array)
    pow(i,i);
  end = std::chrono::system_clock::now();
  std::chrono::duration<double> elapsed_seconds = end-start;
  std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n\n";

  std::cout << "timing ptask" << std::endl;
  start = std::chrono::system_clock::now();
  std::packaged_task<double(double,double)> myTask(pow);
  for (auto& i : array)
  
      myTask(i, i);
      myTask.get_future().wait();
      myTask.reset();
  
  elapsed_seconds = std::chrono::system_clock::now()-start;
  std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n\n";
  return 0;

【讨论】:

嗨@Leonardo,感谢您的详尽回答。我认为您可以得出结论:创建 packaged_task 成本高昂 - 使用它将工作卸载到其他内核仅适用于大量工作。

以上是关于std::packaged_task 真的很贵吗?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 std::future 从 std::packaged_task 和 std::async 返回不同?

并非所有 std::packaged_task 在 std::async 调用中执行

线程 packaged_task future

何时在 async 或 packaged_task 上使用 promise?

何时更喜欢 lambda 而不是带有 std::async 的打包任务? [复制]

packaged_task 和 async 有啥区别