为什么 `std::this_thread::yield()` 比 `std::this_thread::sleep_for(0s)` 慢10倍?

4

只是测试这两个小程序,

#include <thread>

int main()
{
    for (int i = 0; i < 10000000; i++)
    {
        std::this_thread::yield();
    }

    return 0;
}

并且:

#include <thread>
#include <chrono>

int main()
{
    using namespace std::literals;

    for (int i = 0; i < 10000000; i++)
    {
        std::this_thread::sleep_for(0s);
    }

    return 0;
}

我在我的系统上(Ubuntu 22.04 LTS,内核版本5.19.0-43-generic)获取了相应的时间。
./a.out  0,33s user 1,36s system 99% cpu 1,687 total

并且:

./a.out  0,14s user 0,00s system 99% cpu 0,148 total

为什么 std::this_thread::yield() 的速度比 std::this_thread::sleep_for(0s) 慢10倍?
注:在g++和clang++之间的计时相似。
编辑:正如答案中指出的,这是STL实现的优化,调用 sleep(0) 实际上要慢300倍(50微秒对比150纳秒)。

2
这是一个实现细节,但是sleep_for(x);通常只会进行一次if (x > 0)的检查,如果x为零,则不执行任何操作。与我理解的yield()不同,yield()实际上会暂停线程并将控制权交还给操作系统。 - freakish
2
这是一个实现细节,但是sleep_for(x);通常只会进行一次if (x>0)的检查,如果x为零,则不会执行任何操作。与我理解的yield()不同,yield()实际上会暂停线程并将控制权交还给操作系统。 - freakish
2
这是一个实现细节,但是sleep_for(x);通常只会进行一次if (x>0)的检查,如果x为零,则不会执行任何操作。与我理解的yield()不同,yield()实际上会暂停线程并将控制权交还给操作系统。 - undefined
1个回答

8

快速查看源代码this_thread::sleep_for

template<typename _Rep, typename _Period>
inline void
sleep_for(const chrono::duration<_Rep, _Period>& __rtime)
{
    if (__rtime <= __rtime.zero())
      return;
    ...

所以 sleep_for(0s) 什么都不做,实际上你的测试程序使用了 0.0 秒的系统时间,基本上是一个完全在用户空间中运行的空循环(实际上我怀疑如果你使用优化进行编译,它会被完全删除

另一方面,yield 调用* sched_yield,这将调用内核空间中的 schedule(),因此至少执行一些逻辑来检查是否有其他线程可调度。

我认为你的 0.33 秒用户空间时间主要是系统调用开销。

* 实际上会跳转到 __libcpp_thread_yield,然后调用 sched_yield,至少在 Linux 上是这样


谢谢。实际上,我们甚至不执行系统调用。顺便说一下,从unistd.h调用sleep(0)似乎要慢得多。 - matovitch
谢谢。实际上,我们甚至不执行系统调用。顺便说一下,从unistd.h调用sleep(0)似乎要慢得多。 - matovitch
一旦线程被让出,它需要等待操作系统再次安排它的下一次运行。这也需要(墙上)的时间。 - Pepijn Kramer
同样,一旦线程放弃执行,它需要等待操作系统再次安排它的下一次运行。这也需要(实际)时间。 - Pepijn Kramer

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