具有Sleep(0)和暂停指令的忙循环有什么不同?

时间:2011-09-20 16:02:38

标签: c++ hardware-interface busy-loop

我想在我的应用程序中等待一个应该立即发生的事件,所以我不想让我的线程等待并稍后将其唤醒。 我想知道使用Sleep(0)和硬件暂停指令有什么区别。

我看不出以下程序的cpu利用率有任何差异。我的问题不是关于省电的考虑因素。

#include <iostream>
using namespace std;
#include <windows.h>

bool t = false;
int main() {
       while(t == false)
       {
              __asm { pause } ;
              //Sleep(0);
       }
}

2 个答案:

答案 0 :(得分:3)

Windows Sleep(0)vs PAUSE指令

让我引用英特尔64和IA-32架构优化参考手册。

  

在多线程实现中,线程同步和产生调度的流行构造   量子到另一个等待执行任务的线程是坐在循环中并发出SLEEP(0)。

     

这些通常称为“睡眠循环”(参见示例#1)。应该注意一个SwitchToThread调用   也可以使用。 “睡眠循环”在线程所在的锁定算法和线程池中很常见   等待工作。

     

这种坐在紧密循环中并使用参数0调用Sleep()服务的结构实际上是一个   带有副作用的轮询循环:

     
      
  • 每次调用Sleep()都会遇到上下文切换的昂贵代价,可能 10000+周期
  •   
  • 它还会受到第3环到第0环转换的成本,这可能 1000+周期
  •   
  • 当没有其他线程等待占有控制权时,此睡眠循环对操作系统起作用   作为一项高度活跃的任务,需要CPU资源,防止操作系统将CPU置于低功耗状态   状态。
  •   

示例#1。未优化的睡眠循环

while(!acquire_lock())
{ Sleep( 0 ); }
do_work();
release_lock();

示例#2。使用PAUSE

的功耗友好睡眠循环
if (!acquire_lock())
{ /* Spin on pause max_spin_count times before backing off to sleep */
    for(int j = 0; j < max_spin_count; ++j)
    { /* intrinsic for PAUSE instruction*/
        _mm_pause();
        if (read_volatile_lock())
        {
            if (acquire_lock()) goto PROTECTED_CODE;
        }
    }
    /* Pause loop didn't work, sleep now */
    Sleep(0);
    goto ATTEMPT_AGAIN;
}
PROTECTED_CODE:
do_work();
release_lock();
  

示例#2显示了使用PAUSE指令使睡眠回路功率友好的技术。

     

通过使用PAUSE指令减慢“旋转等待”,多线程软件获得:

     
      
  • 通过促进等待任务从繁忙的等待中更容易获取资源的表现。
  •   
  • 通过在旋转时使用较少的管道部分来节省电力。
  •   
  • 消除由于开销导致的绝大多数不必要执行的指令   睡觉(0)电话。
  •   
     

在一个案例研究中,这项技术实现了4.3倍的性能提升,在处理器上节省了21%的功率,在平台级别节省了13%的功耗。

在Skylake Microarchitecture中暂停延迟

  

PAUSE指令通常用于在位于同一处理器内核中的两个逻辑处理器上执行的软件线程,等待锁定被释放。这种短暂的等待循环往往会持续数十到数百个周期,因此在性能方面,等待占用CPU而不是屈服于OS更有利。当等待循环预计持续数千个周期或更长时间时,最好通过调用OS同步API函数之一(例如Windows操作系统上的WaitForSingleObject)来操作系统。

     

PAUSE指令旨在:

     
      
  • 使用竞争性共享硬件资源临时提供兄弟逻辑处理器(准备好进入退出循环的进程)。兄弟逻辑处理器可以在Skylake微体系结构中使用的竞争共享的微体系结构资源是:(1)解码ICache,LSD和IDQ中的更多前端插槽; (2)RS中有更多执行槽。
  •   
  • 与在以下配置中执行等效的自旋循环指令序列相比,节省了处理器内核所消耗的功率:(1)一个逻辑处理器处于非活动状态(例如,进入C状态); (2)同一内核中的两个逻辑处理器都执行PAUSE指令; (3)禁用HT(例如使用BIOS选项)。
  •   
     

上一代微体系结构中PAUSE指令的延迟大约是10个周期,而在Skylake微体系结构上,它已延长到多达140个周期。

     

增加的延迟(允许更有效地利用竞争共享的微体系结构资源到逻辑处理器以准备推进进度)对高线程应用程序产生1-2%的小的正面性能影响。如果在执行固定数量的循环PAUSE指令时未阻止前进,则预计对较少线程的应用程序的影响可以忽略不计。

     

在2核和4核系统中,功耗也很小。随着PAUSE延迟显着增加,对PAUSE延迟敏感的工作负载将遭受一些性能损失。

您可以在&#34;英特尔64和IA-32架构优化参考手册&#34;中找到有关此问题的更多信息。和#34;英特尔64和IA-32架构软件开发人员手册&#34;以及代码示例。

我的意见

最好使程序逻辑以不需要Sleep(0)和PAUSE指令的方式流动。换句话说,完全避免“旋转等待”循环。相反,请使用高级同步功能,例如WaitForMultipleObjects()SetEvent()等。这种高级同步功能是编写程序的最佳方式。如果从性能,效率和节能方面分析可用工具(在您的处置中) - 更高级别的功能是最佳选择。虽然它们也遭受昂贵的上下文切换和环3到0环转换,但这些费用并不常见,而且与你在所有“自旋等待”PAUSE周期组合或周期中所花费的总和相比。与睡眠(0)。

在支持超线程的处理器上,“自旋等待”循环会占用处理器执行带宽的很大一部分。执行自旋等待循环的一个逻辑处理器可能严重影响另一逻辑处理器的性能。这就是为什么有时候禁用超线程可能会提高性能的原因,正如一些人所指出的那样。

在程序逻辑工作流程中持续轮询设备或文件或状态更改可能会导致计算机消耗更多电量,给内存和总线带来压力并提供不必要的页面错误(使用Windows中的任务管理器查看哪个应用程序在处于空闲状态时会产生大多数页面错误,在后台等待用户输入 - 这些是效率最低的应用程序,因为它们使用上面提到的极化)。尽可能减少轮询(包括旋转循环)并使用事件驱动的意识形态和/或框架(如果可用) - 这是我强烈推荐的最佳实践。您的应用程序应该始终保持睡眠状态,等待事先设置的多个事件。

事件驱动应用程序的一个很好的例子是Nginx,最初是为类Unix操作系统编写的。由于操作系统提供了各种功能和方法来通知您的应用程序,因此请使用这些通知而不是轮询设备状态更改。只需让您的程序无限畅通,直到通知到达或用户输入到达为止。使用这种技术可以减少代码轮询数据源状态的开销,因为代码可以在状态发生变化时异步获取通知。

答案 1 :(得分:1)

Sleep是一个系统调用,它允许操作系统在允许调用者继续(即使参数为0)之前将CPU时间重新安排到任何其他进程(如果可用)。

__asm {pause};不可移植。

嗯,Sleep既不是,也不是CPU级别,而是系统库级别。