为什么std::async与简单的分离线程相比速度较慢



有人多次告诉我,我应该使用std::async进行fire&忘记使用std::launch::async参数的任务类型(所以它最好在新的执行线程上发挥神奇的作用)。

受到这些声明的鼓舞,我想看看std::async与相比如何

  • 顺序执行
  • 一个简单的分离CCD_ 4
  • 我的简单异步"实现">

我天真的异步实现看起来是这样的:

template <typename F, typename... Args>
auto myAsync(F&& f, Args&&... args) -> std::future<decltype(f(args...))>
{
std::packaged_task<decltype(f(args...))()> task(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
auto future = task.get_future();
std::thread thread(std::move(task));
thread.detach();
return future;
}

这里没有什么特别的,将函子f及其参数打包到std::packaged task中,在分离的新std::thread上启动它,并从任务中返回std::future

现在用std::chrono::high_resolution_clock:测量执行时间的代码

int main(void)
{
constexpr unsigned short TIMES = 1000;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < TIMES; ++i)
{
someTask();
}
auto dur = std::chrono::high_resolution_clock::now() - start;
auto tstart = std::chrono::high_resolution_clock::now();
for (int i = 0; i < TIMES; ++i)
{
std::thread t(someTask);
t.detach();
}
auto tdur = std::chrono::high_resolution_clock::now() - tstart;
std::future<void> f;
auto astart = std::chrono::high_resolution_clock::now();
for (int i = 0; i < TIMES; ++i)
{
f = std::async(std::launch::async, someTask);
}
auto adur = std::chrono::high_resolution_clock::now() - astart;
auto mastart = std::chrono::high_resolution_clock::now();
for (int i = 0; i < TIMES; ++i)
{
f = myAsync(someTask);
}
auto madur = std::chrono::high_resolution_clock::now() - mastart;
std::cout << "Simple: " << std::chrono::duration_cast<std::chrono::microseconds>(dur).count() <<
std::endl << "Threaded: " << std::chrono::duration_cast<std::chrono::microseconds>(tdur).count() <<
std::endl << "std::sync: " << std::chrono::duration_cast<std::chrono::microseconds>(adur).count() <<
std::endl << "My async: " << std::chrono::duration_cast<std::chrono::microseconds>(madur).count() << std::endl;
return EXIT_SUCCESS;
}

其中someTask()是一个简单的方法,我等待了一段时间,模拟完成了一些工作:

void someTask()
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

最后,我的结果:

  • 顺序:1263615
  • 螺纹:47111
  • std::sync:821441
  • 我的异步:30784

有人能解释这些结果吗?std::aysnc似乎比我天真的实现慢得多,或者只是简单明了的分离的std::thread。为什么是?在这些结果之后,是否有理由使用std::async

(注意,我也用clang++和g++做了这个基准测试,结果非常相似)

更新:

在阅读了Dave S的回答后,我更新了我的小基准如下:

std::future<void> f[TIMES];
auto astart = std::chrono::high_resolution_clock::now();
for (int i = 0; i < TIMES; ++i)
{
f[i] = std::async(std::launch::async, someTask);
}
auto adur = std::chrono::high_resolution_clock::now() - astart;

因此,std::future现在并没有在每次运行时被销毁,因此也没有被加入。在代码中的这个更改之后,std::async产生了与我的实现类似的结果&分离std::threads.

一个关键区别是,当future被销毁时,或者在您的情况下,用新值替换时,async返回的future会加入线程。

这意味着它必须执行someTask()并加入线程,这两者都需要时间。你的其他测试都没有这样做,它们只是独立地产生它们。

sts::async返回一个特殊的std::future。这个未来有一个做.wait()~future

所以你们的例子根本不同。慢一点的人实际上是在你的时间里完成任务的。速度快的人只是把任务排成一排,忘记了如何知道任务已经完成。由于让线程持续超过main末尾的程序的行为是不可预测的,因此应该避免这种情况

比较任务的正确方法是在生成时存储生成的future,并且在计时器结束之前将其全部存储为.wait()/.join(),或者在计时器到期之前避免销毁对象。然而,最后一种情况使Sewential版本看起来比实际情况更糟

在开始下一个测试之前,您确实需要加入/等待,否则您将从他们的时间中窃取资源。

请注意,移动的期货会从源中删除等待。

最新更新