为什么 Sleep() 会使后续代码减慢 40 毫秒?
Why does Sleep() slow down subsequent code for 40ms?
我最初是在 coderanch.com 上询问过这个问题的,所以如果你试图在那里帮助我,谢谢,并且不要觉得有必要重复这项工作。不过,coderanch.com 主要是一个 Java 社区,这似乎(经过一些研究)确实是一个 Windows 问题,所以我和那里的同事认为这可能是一个更合适的地方寻求帮助。
我编写了一个简短的程序,它可以在 Windows 性能计数器上旋转直到 33 毫秒过去,或者调用 Sleep(33)。前者没有表现出意想不到的效果,但后者似乎(不一致地)将后续处理减慢了大约 40 毫秒(或者,或者它对从性能计数器编辑的值 return 产生了一些影响)。自旋或 Sleep() 之后,程序调用例程 运行InPlace(),自旋 2 毫秒,计算它查询性能计数器的次数,然后 returning 该数字。
当最初的 33 毫秒延迟通过旋转完成时,运行InPlace() 的迭代次数往往是(在我的 Windows 10,XPS-8700 上)大约 250,000。它会有所不同,可能是由于其他系统开销,但它会在 250,000 左右平滑变化。
现在,当通过调用 Sleep() 完成初始延迟时,会发生一些奇怪的事情。很多 运行InPlace() return 的调用接近 250,000,但其中相当一部分 return 接近 50,000。同样,范围在 50,000 左右变化,相当平稳。但是,它显然是其中之一的平均值,在 80,000 到 150,000 之间几乎没有任何 return。如果我在每次延迟后调用 运行InPlace() 100 次,而不是只调用一次,那么在第 20 次调用之后,它永远不会 return 在较小的范围内进行多次迭代。由于 运行InPlace() 运行s 持续 2 毫秒,这意味着我观察到的行为在 40 毫秒后消失。如果我有 运行InPlace() 运行 4 毫秒而不是 2 毫秒,它永远不会 return 在第 10 次调用后在较小范围内进行多次迭代,因此,再次,行为消失40 毫秒(同样,如果 运行InPlace() 运行 仅持续 1 毫秒;该行为在第 40 次调用后消失)。
这是我的代码:
#include "stdafx.h"
#include "Windows.h"
int runInPlace(int msDelay)
{
LARGE_INTEGER t0, t1;
int n = 0;
QueryPerformanceCounter(&t0);
do
{
QueryPerformanceCounter(&t1);
n++;
} while (t1.QuadPart - t0.QuadPart < msDelay);
return n;
}
int _tmain(int argc, _TCHAR* argv[])
{
LARGE_INTEGER t0, t1;
LARGE_INTEGER frequency;
int n;
QueryPerformanceFrequency(&frequency);
int msDelay = 2 * frequency.QuadPart / 1000;
int spinDelay = 33 * frequency.QuadPart / 1000;
for (int i = 0; i < 100; i++)
{
if (argc > 1)
Sleep(33);
else
{
QueryPerformanceCounter(&t0);
do
{
QueryPerformanceCounter(&t1);
} while (t1.QuadPart - t0.QuadPart < spinDelay);
}
n = runInPlace(msDelay);
printf("%d \n", n);
}
getchar();
return 0;
}
这是我在使用 Sleep() 进行延迟时得到的典型输出:
56116
248936
53659
34311
233488
54921
47904
45765
31454
55633
55870
55607
32363
219810
211400
216358
274039
244635
152282
151779
43057
37442
251658
53813
56237
259858
252275
251099
这是我旋转以创建延迟时得到的典型输出:
276461
280869
276215
280850
188066
280666
281139
280904
277886
279250
244671
240599
279697
280844
159246
271938
263632
260892
238902
255570
265652
274005
273604
150640
279153
281146
280845
248277
谁能帮我理解这种行为? (请注意,我已经在五台计算机上尝试过这个用 Visual C++ 2010 Express 编译的程序。它只在我拥有的两台最快的机器上显示了这种行为。)
这听起来像是由于计算机不忙时 CPU 将 运行 的时钟速度降低 (SpeedStep)。当计算机空闲时(如睡眠),时钟速度会降低以降低功耗。在较新的 CPUs 上,这可能是所列时钟速度的 35% 或更少。一旦计算机再次变得繁忙,在 CPU 再次加速之前会有一小段延迟。
您可以关闭此功能(在 BIOS 中或通过将电源计划高级设置中 "Processor power management" 下的 "Minimum processor state" 设置更改为 100%。
除了@1201ProgramAlarm所说的(很可能,现代处理器非常喜欢尽可能降频),也可能是缓存预热问题。
当您要求休眠一段时间时,调度程序通常会为下一个 CPU 时间量程安排另一个 thread/process,这意味着缓存(指令缓存、数据缓存、TLB、分支预测器data, ...) 当您的代码重新获得 CPU 时,相对于您的进程将再次 "cold"。
我最初是在 coderanch.com 上询问过这个问题的,所以如果你试图在那里帮助我,谢谢,并且不要觉得有必要重复这项工作。不过,coderanch.com 主要是一个 Java 社区,这似乎(经过一些研究)确实是一个 Windows 问题,所以我和那里的同事认为这可能是一个更合适的地方寻求帮助。
我编写了一个简短的程序,它可以在 Windows 性能计数器上旋转直到 33 毫秒过去,或者调用 Sleep(33)。前者没有表现出意想不到的效果,但后者似乎(不一致地)将后续处理减慢了大约 40 毫秒(或者,或者它对从性能计数器编辑的值 return 产生了一些影响)。自旋或 Sleep() 之后,程序调用例程 运行InPlace(),自旋 2 毫秒,计算它查询性能计数器的次数,然后 returning 该数字。
当最初的 33 毫秒延迟通过旋转完成时,运行InPlace() 的迭代次数往往是(在我的 Windows 10,XPS-8700 上)大约 250,000。它会有所不同,可能是由于其他系统开销,但它会在 250,000 左右平滑变化。
现在,当通过调用 Sleep() 完成初始延迟时,会发生一些奇怪的事情。很多 运行InPlace() return 的调用接近 250,000,但其中相当一部分 return 接近 50,000。同样,范围在 50,000 左右变化,相当平稳。但是,它显然是其中之一的平均值,在 80,000 到 150,000 之间几乎没有任何 return。如果我在每次延迟后调用 运行InPlace() 100 次,而不是只调用一次,那么在第 20 次调用之后,它永远不会 return 在较小的范围内进行多次迭代。由于 运行InPlace() 运行s 持续 2 毫秒,这意味着我观察到的行为在 40 毫秒后消失。如果我有 运行InPlace() 运行 4 毫秒而不是 2 毫秒,它永远不会 return 在第 10 次调用后在较小范围内进行多次迭代,因此,再次,行为消失40 毫秒(同样,如果 运行InPlace() 运行 仅持续 1 毫秒;该行为在第 40 次调用后消失)。
这是我的代码:
#include "stdafx.h"
#include "Windows.h"
int runInPlace(int msDelay)
{
LARGE_INTEGER t0, t1;
int n = 0;
QueryPerformanceCounter(&t0);
do
{
QueryPerformanceCounter(&t1);
n++;
} while (t1.QuadPart - t0.QuadPart < msDelay);
return n;
}
int _tmain(int argc, _TCHAR* argv[])
{
LARGE_INTEGER t0, t1;
LARGE_INTEGER frequency;
int n;
QueryPerformanceFrequency(&frequency);
int msDelay = 2 * frequency.QuadPart / 1000;
int spinDelay = 33 * frequency.QuadPart / 1000;
for (int i = 0; i < 100; i++)
{
if (argc > 1)
Sleep(33);
else
{
QueryPerformanceCounter(&t0);
do
{
QueryPerformanceCounter(&t1);
} while (t1.QuadPart - t0.QuadPart < spinDelay);
}
n = runInPlace(msDelay);
printf("%d \n", n);
}
getchar();
return 0;
}
这是我在使用 Sleep() 进行延迟时得到的典型输出:
56116 248936 53659 34311 233488 54921 47904 45765 31454 55633 55870 55607 32363 219810 211400 216358 274039 244635 152282 151779 43057 37442 251658 53813 56237 259858 252275 251099
这是我旋转以创建延迟时得到的典型输出:
276461 280869 276215 280850 188066 280666 281139 280904 277886 279250 244671 240599 279697 280844 159246 271938 263632 260892 238902 255570 265652 274005 273604 150640 279153 281146 280845 248277
谁能帮我理解这种行为? (请注意,我已经在五台计算机上尝试过这个用 Visual C++ 2010 Express 编译的程序。它只在我拥有的两台最快的机器上显示了这种行为。)
这听起来像是由于计算机不忙时 CPU 将 运行 的时钟速度降低 (SpeedStep)。当计算机空闲时(如睡眠),时钟速度会降低以降低功耗。在较新的 CPUs 上,这可能是所列时钟速度的 35% 或更少。一旦计算机再次变得繁忙,在 CPU 再次加速之前会有一小段延迟。
您可以关闭此功能(在 BIOS 中或通过将电源计划高级设置中 "Processor power management" 下的 "Minimum processor state" 设置更改为 100%。
除了@1201ProgramAlarm所说的(很可能,现代处理器非常喜欢尽可能降频),也可能是缓存预热问题。
当您要求休眠一段时间时,调度程序通常会为下一个 CPU 时间量程安排另一个 thread/process,这意味着缓存(指令缓存、数据缓存、TLB、分支预测器data, ...) 当您的代码重新获得 CPU 时,相对于您的进程将再次 "cold"。