std::~future 的开销是多少?

3

std::future的析构开销是多少? 当我阅读这个PDF时,注意到:

// Example 1
// (a)
{
  async( []{ f(); } ); 
  async( []{ g(); } ); 
}
// (b)
{
  auto f1 = async( []{ f(); } );
  auto f2 = async( []{ g(); } );
}

用户经常惊讶地发现 (a) 和 (b) 的行为不同,因为通常情况下,如果我们最终决定不关心返回值并且这样做不会改变程序的含义,我们就忽略 (或者不看) 返回值。
但是我在 quickbench 中检查了一下,结果与我所想的相反。我是否漏掉了讨论的要点?

https://quick-bench.com/q/L0HtxBWmgvswK7Q90AqOPmsg_F8

2个回答

6

基准测试

我怀疑 std::this_thread::sleep_for(1000ms) 是罪魁祸首。让线程休眠并再次唤醒不是非常精确的操作。有时候线程会被提前唤醒,有时候又太早了,所以必须将其放回睡眠状态。如果你移除休眠(或者使用一个确定性操作),你会发现它们基本上是相同的。

async问题

浏览PDF文件,Herb所讨论的问题是如果您链接多个异步调用,则根据您是否将future分配给变量,它们可能会以不同的方式执行。在所有情况下,future的析构函数仍然会运行,只是在不同的时间点上。请记住,C++无法根据返回类型区分函数重载。这意味着,无论您是否使用返回值,它仍将被构造,因此必须被销毁。唯一的区别是销毁发生的时间。

场景(a)不同,因为两个异步调用是同步发生的,因为future立即被销毁,从而阻塞了线程:

{
    async( []{ f(); } ); 
    // ^ this future is a temporary, it will be destroyed here, directly after the function call

    async( []{ g(); } );
    // ^ this future is a temporary, it will be destroyed here, directly after the function call
}

另一方面,情况(b)中,两个异步调用都可以在任何一个 future 被销毁之前发生,因为两个 future 只有在这两个异步调用都发生之后才会被销毁。
{
    auto f1 = async( []{ f(); } );
    auto f2 = async( []{ g(); } ); 
}
// ^ variables went out of scope here, both futures will be destroyed now

4

这篇文章的观点是,在进行以下操作时:

async( []{ f(); } ); 
async( []{ g(); } ); 

每次async调用后的隐藏析构函数会使当前线程等待async完成,从而使代码变为同步执行。也就是说,代码会执行以下步骤:

  1. 启动任务1
  2. 等待任务1完成
  3. 启动任务2
  4. 等待任务2完成

更改为:

auto f1 = async( []{ f(); } );
auto f2 = async( []{ g(); } );

假设 std::async 实际上使用了多个线程, 两个任务在未来对象销毁之前启动,从而允许任务并行运行。

您的基准测试每次循环只运行一个任务,因此无法演示此问题。我想你的结果中大部分差异来自于 std::this_thread::sleep_for 的随机性。

将基准测试更改为两个任务确实显示出一些小差异:https://quick-bench.com/q/wy6yPq4yMBi_VGSJ7HI_hLccZmc (虽然可能仅在误差范围内)。我不确定 quickbench 网站是否提供多个CPU?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接