【问题标题】:Windows Sleep Function Extremely SlowWindows 睡眠功能极慢
【发布时间】:2022-01-07 19:28:41
【问题描述】:

我正在通过 Windows.h 使用 Sleep 命令制作程序,并且在 Windows 10 而不是 Windows 7 上运行我的程序时遇到了令人沮丧的差异。我将我的程序简化为下面的程序,它表现出与我相同的行为更复杂的程序。

在 Windows 7 上,这个 5000 计数循环以 1 毫秒的时间运行睡眠功能。这需要 5 秒才能完成。

在 Windows 10 上,当我运行完全相同的程序(完全相同的二进制可执行文件)时,该程序几乎需要一分钟才能完成。

对于我的应用程序来说,这是完全不可接受的,因为我需要有 1ms 的时间延迟才能与我正在使用的硬件进行交互。

我还尝试了另一篇文章的建议以使用 select() 命令(通过 winsock2),但该命令也无法延迟 1 毫秒。我已经在多台 Windows 7 和 Windows 10 PC 上尝试过这个程序,问题的根本原因总是指向使用 Windows 10 而不是 Windows 7。该程序总是在大约 5 秒内在众多 Windows 7 PC 和多个 Windows 上运行我测试过的 10 台电脑的持续时间要长得多~60 秒。

我一直在使用 Microsoft Visual Studio Express 2010 (C/C++) 和 Microsoft Visual Studio Express 2017 (C/C++) 来编译程序。 Visual Studio 的版本不影响结果。

我还将编译选项从“调试”更改为“发布”,并尝试优化编译器,但这也无济于事。

任何建议将不胜感激。

#include <stdio.h>
#include <Windows.h>

#define LOOP_COUNT      5000

int main()
{
    int i = 0;

    for (i; i < LOOP_COUNT; i++){
        Sleep(1);
    }

    return 0;
}

【问题讨论】:

  • Windows 不是实时操作系统,Sleep() 没有 1ms 的精度。对于这种精度,请使用高性能计数器(请参阅QueryPerformance(Counter|Frequency) 函数),甚至只是一个自旋计数器。
  • 运行发布可执行文件和调试可执行文件之间的运行时也会有显着差异。在我的系统上,它通常是 10 倍的差异。
  • @ZachariahRabatah:我想知道你是否读过the documentation,因为它明确表示“要提高睡眠间隔的准确性,请调用timeGetDevCaps 函数来确定支持的最小计时器分辨率和timeBeginPeriod 函数将计时器分辨率设置为最小值。调用timeBeginPeriod 时要小心,因为频繁调用会显着影响系统时钟、系统电源使用和调度程序..."

标签: c++ c windows windows-10 windows-7


【解决方案1】:

我需要有 1 毫秒的时间延迟才能与我正在使用的硬件进行交互

Windows 不适合这项工作。

如果您坚持使用这个错误的工具,您将不得不做出妥协(例如使用忙等待并接受相应的电池寿命不佳)。

您可以使用timeBeginPeriod(1) 使Sleep() 更准确,但这取决于您的硬件外围设备对“一毫秒”延迟的限制——这是某个范围的最小值、最大值还是中间值? -- 它仍然会以非零概率无法满足您的时序要求。

timeBeginPeriod 函数请求周期性计时器的最小分辨率。

与具有严格时序容差的硬件通信的正确解决方案是嵌入式微控制器,它通过一些非常灵活的接口(例如 UART 或以太网)与 Windows PC 通信,缓冲数据,并使用硬件定时器生成定义明确的信号时机。

在某些情况下,您可以使用 Windows PC 中已经存在的嵌入式电路,例如“声卡”功能。

【讨论】:

  • 感谢您的反馈。我明白 Windows 不应该是适合这项工作的工具。但是,我已经能够使用许多不同的 Windows 7 机器使这项工作正常工作。我的猜测是 Windows 7 和 Windows 10 之间存在一些默认差异。也许 Windows 10 上的这种差异与节省电力/电池过度使用有关,因此随机程序不会占用电池寿命或使处理器过载。我将尝试使用 timeBeginPeriod 函数,看看它是如何工作的。
  • 以 timeBeginPeriod(1) 运行;确实提高了程序速度,但仍比预期慢 2 倍(10 秒而不是 5 秒)。我会尝试看看我与之交互的硬件是否可以缓冲,这样我就不必像以前那样快速拨打电话了。
  • Ben 的评论直截了当地指出,对于需要这种时间粒度的程序来说,Windows 并不是最佳平台。也就是说,它并没有说明您在 7 和 10 之间看到的性能差异。您是否在多个系统上尝试过这个?这里似乎有些不对劲......
  • @mzimmers:操作系统版本相关的性能差异是由于documentation page of timeBeginPeriod 中的以下注释:“从 Windows 10 版本 2004 开始,此功能不再影响全局计时器分辨率。对于调用此函数的进程,Windows 使用任何进程请求的最低值(即最高分辨率)。对于没有调用此函数的进程,Windows 不保证比默认系统分辨率更高的分辨率。"
  • @mzimmers:显然,在 OP 的 Windows 7 部署中,其他一些进程已经全局调整了计时器分辨率,并且 OP 的代码受益。在从 2004 版开始的 Windows 10 上,无论其他进程在做什么,OP 的代码都会看到 10 毫秒或 15 毫秒的默认时间间隔。独立于其他进程的意外影响通常被认为是一件好事 - 如果不相关的后台进程集发生变化,OP 的代码也会在 Windows 7 上开始失败。
【解决方案2】:

@BenVoigt 和@mzimmers 感谢您的回复和建议。我确实为这个问题找到了一个独特的解决方案,这个解决方案的灵感来自我在下面直接链接的帖子。

Units of QueryPerformanceFrequency

在这篇文章中,BrianP007 编写了一个函数来查看 Sleep(1000) 命令的执行速度。然而,当我在玩的时候,我意识到 Sleep() 接受 0。因此,我使用了与链接帖子类似的结构来查找循环直到达到 1ms 的 delta t 所需的时间。

出于我的目的,我将 i 增加了 100,但它可以增加 10 或 1,以便更准确地估计我应该是什么。

获得 i 的值后,您可以使用该值在您的机器上获得大约 1 毫秒的延迟。如果你在一个循环中运行这个函数(我运行了 100 次),我可以得到从 i = 3000 到 i = 6000 的任何值。但是,我的机器平均在 5500 左右。这种分布可能是由于抖动/时钟频率变化通过处理器中的时间。

下面的processor_check() 函数只找出for 循环参数应该返回什么值;实际的“计时器”只需要在其中包含 Sleep(0) 的 for 循环即可在机器上运行分辨率约为 1 毫秒的计时器。

虽然这种方法并不完美,但它比使用 Sleep(1) 更接近并且效果更好。我必须对此进行更彻底的测试,但请让我知道这是否也适合您。如果您自己的应用程序需要它,请随时使用下面的代码。这段代码应该可以直接复制粘贴到 Visual Studio 中一个空的命令提示符 C 程序中而无需修改。

/*ZKR Sleep_ZR()*/

#include "stdio.h"
#include <windows.h>

/*Gets for loop value*/
int processor_check()
{
    double delta_time = 0;
    int i = 0;
    int n = 0;

    while(delta_time < 0.001){
        LARGE_INTEGER sklick, eklick, cpu_khz;

        QueryPerformanceFrequency(&cpu_khz);
        QueryPerformanceCounter(&sklick);
        for(n = 0; n < i; n++){
            Sleep(0);
        }
        QueryPerformanceCounter(&eklick);
        delta_time = (eklick.QuadPart-sklick.QuadPart) / (double)cpu_khz.QuadPart;

        i = i + 100;
    }
    return i;
}

/*Timer*/    
void Sleep_ZR(int cnt)
{
    int i = 0;
    for(i; i < cnt; i++){
        Sleep(0);
    }
}


/*Main*/
int main(int argc, char** argv)
{
    double average = 0;
    int i = 0;
    
    /*Single use*/
    int loop_count = processor_check();
    Sleep_ZR(loop_count);
    
    /*Average based on processor to get more accurate Sleep_ZR*/
    for(i = 0; i < 100; i++){
        loop_count = processor_check();
        average = average + loop_count;
    }
    average = average / 100;
    printf("Average: %f\n", average);

    /*10 second test*/
    for (i = 0; i < 10000; i++){
        Sleep_ZR((int)average);
    }

    return 0;
}

【讨论】:

  • 这将对系统上运行的其他任务极为敏感。
  • 您最好编写一个忙等待循环,直到经过所需的时间(由QueryPerformanceCounter 衡量),然后将Sleep(0) 扔到该忙循环的主体中。它将具有相同的可观察行为(调用Sleep(0)“足够”次)但对负载变化更加稳健。
  • Windows 操作系统会在后台执行很多操作,即使没有其他用户活动(例如运行 Web 浏览器)也是如此。例如,下载 Windows 更新或 ping Microsoft 服务器以检测网络中的变化。这是 Windows 不适合做这件事的原因之一。
  • @BenVoigt 是的,我同意,但是在工作中,我需要为这个特定的应用程序使用 Windows。我正在使用的硬件内部应该确实有一个缓冲区,这样我就不必每毫秒都访问它,但是这个硬件是独一无二的,没有可用的功能。我使用的所有其他硬件都具有存储缓冲数据的能力,因此我不必跑得那么快就可以访问数据。
  • 所以使用忙等待循环,终止条件基于QueryPerformanceCounter。只是不要期望循环迭代的数量保持不变——它会根据 CPU 频率缩放以及 Windows 决定在那个时刻唤醒的后台任务而变化。把你的方程换成delta_time,求解eklick,然后用它作为循环终止条件。
猜你喜欢
  • 1970-01-01
  • 2011-03-23
  • 2021-04-12
  • 2011-04-08
  • 2011-02-03
  • 2014-12-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多