和暂停指令的繁忙循环有什么不同

和暂停指令的繁忙循环有什么不同

本文介绍了睡眠(0)和暂停指令的繁忙循环有什么不同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我看不出以下程序的 CPU 利用率有任何差异.我的问题不是关于节能方面的考虑.

#include 使用命名空间标准;#include 布尔 t = 假;int main() {而(t == 假){__asm { 暂停 } ;//睡眠(0);}}
解决方案

Windows Sleep(0) vs The PAUSE 指令

让我引用 Intel 64 和 IA-32 架构优化参考手册.

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

这些通常称为睡眠循环"(参见示例 1).需要注意的是,一个 SwitchToThread 调用也可以使用.睡眠循环"在锁定算法和线程池中很常见,因为线程是等待工作.

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

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

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

while(!acquire_lock()){睡眠(0);}做工作();释放锁();

示例#2.使用 PAUSE 的功耗友好睡眠循环

if (!acquire_lock()){/* 在返回休眠前旋转暂停 max_spin_count 次 */for(int j = 0; j 

示例#2 展示了使用 PAUSE 指令使睡眠循环功耗友好的技术.

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

  • 通过促进等待任务从繁忙等待中更轻松地获取资源来提高性能.
  • 通过在旋转时使用更少的管道部件来节省能源.
  • 消除了由于开销导致的绝大多数不必要执行的指令Sleep(0) 调用.

在一个案例研究中,这项技术实现了 4.3 倍的性能提升,这意味着处理器节能 21%,平台级节能 13%.

Skylake 微架构中的暂停延迟

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

PAUSE 指令旨在:

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

上一代微架构中 PAUSE 指令的延迟约为 10 个周期,而在 Skylake 微架构中,它已扩展到多达 140 个周期.

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

在 2 核和 4 核系统中还有一个小的功耗优势.由于 PAUSE 延迟显着增加,对 PAUSE 延迟敏感的工作负载会遭受一些性能损失.

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

我的意见

最好使程序逻辑流的方式既不需要 Sleep(0) 也不需要 PAUSE 指令.换句话说,完全避免自旋-等待"循环.相反,使用高级同步函数,如 WaitForMultipleObjects()SetEvent() 等.这种高级同步函数是编写程序的最佳方式.如果您从性能、效率和节能方面分析可用工具(根据您的意愿) - 更高级别的功能是最佳选择.尽管它们也遭受昂贵的上下文切换和环 3 到环 0 的转换,但与您在所有旋转等待"暂停周期组合或周期中花费的总和相比,这些费用很少见并且是合理的与睡眠(0).

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

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

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

I would like to wait on an event in my app which supposed to happen immediately, so I don't want to put my thread on wait and wake it up later.I wonder what are the difference between using Sleep(0) and hardware pause instruction.

I cannot see any differences of cpu utilization for the following program. My question isn't about power saving considerations.

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

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

Windows Sleep(0) vs The PAUSE instruction

Let me quote from the Intel 64 and IA-32 Architectures Optimization Reference Manual.

Example #1. Unoptimized Sleep Loop

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

Example #2. Power Consumption Friendly Sleep Loop Using 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();

Pause Latency in Skylake Microarchitecture

You can find more information on this issue in the "Intel 64 and IA-32 Architectures Optimization Reference Manual" and "Intel 64 and IA-32 Architectures Software Developer’s Manual", along with the code samples.

My Opinion

It is better make program logic flow in such a way that neither Sleep(0) nor the PAUSE instruction are ever needed. In other words, avoid the "spin-wait" loops altogether. Instead, use high-level synchronization functions like WaitForMultipleObjects(), SetEvent(), and so on. Such high-level synchronization functions are the best way to write the programs. If you analyze available tools (at your disposition) from the terms of performance, efficiency and power saving - the higher-level functions are the best choice. Although they also suffer from expensive context switches and ring 3 to ring 0 transitions, these expenses are infrequent and are more than reasonable, compared to what you would have spent in total for all the "spin-wait" PAUSE cycles combined, or the cycles with with Sleep(0).

On a processor supporting hyper-threading, "spin-wait" loops can consume a significant portion of the execution bandwidth of the processor. One logical processor executing a spin-wait loop can severely impact the performance of the other logical processor. That's why sometimes disabling hyper-threading may improve performance, as have been pointed out by some people.

Consistently polling for devices or file or state changes in the program logic workflow can cause the computer to consume more power, to put stress on memory and the bus and to provide unnecessary page faults (use the Task Manager in Windows to see which applications produce most page faults while in the idle state, waiting for user input in the background - these are most inefficient applications since they are using the poling above mentioned). Minimize polling (including the spin-loops) whenever possible and use an event driven ideology and/or a framework if available - this is the best practice that I highly recommend. You application should literally sleep all the time, waiting for multiple events set up in advance.

A good example of an event-driven application is Nginx, initially written for unix-like operating systems. Since the operating systems provide various functions and methods to notify your application, use these notifications instead of polling for device state changes. Just let your program to sleep infinitely until a notification will arrive or a user input will arrive. Using such a technique reduces the overhead for the code to poll the status of the data source, because the code can get notifications asynchronously when status changes happen.

这篇关于睡眠(0)和暂停指令的繁忙循环有什么不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-20 02:36