C++
解决WINDOWS SLEEP精度问题
  • By刘立博
  • 2021-01-08 23:40:29
  • 4222人已阅读

关于时间分片的栗子

 

假设CPU时间片为50毫秒,有A、B两个线程并行执行。线程A在某个时间分片结尾阻塞60毫秒后,实际在那个时间点才能真正被唤醒呢?理论上在110毫秒的时间点会被唤醒,但因为110毫秒正处于线程B的时间片,所以实际会在150毫秒的时间点线程A才会被唤醒,比我们的预期慢了40毫秒。

windows操作系统SLEEP精度测试

我们使用C++库中的this_thread::sleep_for方法进行精度测试,假设每休眠20毫秒FPS加1,那么FPS在1秒后理论应该是50,但实际结果如何呢?

 

测试代码

clock_t begin = clock();
int fps = 0;

for (;;)
{
	++fps;
	this_thread::sleep_for(20ms);
	if ((clock() - begin) / (CLOCKS_PER_SEC / 1000) > 1000)
	{
		cout << "THREAD SLEEP FPS = " << fps << endl;
		break;
	}
}

 

测试结果

 

FPS仅为32,为什么会有这么大的误差呢?这是因为windows线程调度的最小时间片是3个量子,在单核/单处理器系统上3个量子等于10毫秒,在多核/多处理器系统上3个量子等于15毫秒。也就是说虽然我们休眠20毫秒,但往往需要30毫秒才能被真正唤醒。

提升sleep精度

由于在多核处理器每个量子约等于5毫秒,所以要提升sleep的精度,就需要提升量子的精度。我们可以使用WINDOWS API中的timeBeginPeriod(1)设置时钟的最小周期为1毫秒,这样一个量子约等于1毫秒,那么最小时间片也就成了3毫秒。

测试代码

#include "windows.h"
#include "MMSystem.h"
#pragma comment(lib, "winmm.lib")

//设置时钟精度
timeBeginPeriod(1);

begin = clock();
fps = 0;
for (;;)
{
	++fps;
	this_thread::sleep_for(20ms);
	if ((clock() - begin) / (CLOCKS_PER_SEC / 1000) > 1000)
	{
		cout << "THREAD SLEEP WITH timeBeginPeriod(1)  FPS = " << fps << endl;
		break;
	}
}

//恢复时钟精度
timeEndPeriod(1);

 

测试结果

 

可见,FPS提升为49,接近理论值。但过小的时间片会造成线程切换过于频繁带来不必要的性能开销,所以在不需要高时钟精度时,需要调用timeEndPeriod()将时钟精度恢复为默认值。