Windows几种sleep精度的测试
在Windows环境下使用Sleep的时候,经常会遇到明明Sleep了1ms,结果被唤醒时所消耗的时间大于1ms,
对于那些精确度稍微高一点的Sleep控制时序,就不起作用了,众所周知Windows的Sleep是基于毫秒级别的,如果需要精确到微秒级,需要另辟蹊径
本文总结了几种常用的控制时序的方式,可以作为一个参考,测试出来的数值并不一定准确,我在我电脑上和另外一台Win7 i5 2.3GHz的电脑上跑出来的结果差别比较大,可以根据自己电脑或者使用环境多跑些数据
源码下载链接:0积分下载链接,没有积分的评论区留言,我改链接
测试类别分别包括:
1、Windows中的原生Sleep
2、C++11的this_thread::sleep_for以及timeBeginPeriod调整定时器精度两个方式
3、socket连接的select的方式
4、多媒体时钟QueryPerformanceCounter的微秒级方式
5、MsgWaitForMultipleObjectsEx
系统环境:Win10,CPU i7-8750 2.2GHz
一、Sleep耗时
先上代码和测试结果
代码:
void Precision_Sleep()
{
std::string buffer;
buffer.assign(BUFFER_SIZE, 0);
buffer.clear();
int i = TEST_TIMES;
uint64_t total_used = 0;
while (i) {
i--;
steady_clock::time_point time_begin = steady_clock::now();
Sleep(Interval_Millisceonds);
steady_clock::time_point time_end = steady_clock::now();
char tmp[128] = {0};
uint64_t used = duration_cast<microseconds>(time_end - time_begin).count();
snprintf(tmp, 128, "%s Sleep %d ms, time used : %lld us\n",
__FUNCTION__, Interval_Millisceonds, used);
total_used += used;
buffer += tmp;
}
printf("%s\n", buffer.c_str());
printf("%s Sleep %d ms, avatar %lld us\n\n",
__FUNCTION__, Interval_Millisceonds,total_used / TEST_TIMES);
}
结果:
这是跑了50次的平均结果,Sleep(1),实际的平均sleep时间大约在1500微秒
二、C++11,sleep_for
代码:
void Precision_sleep_for()
{
std::string buffer;
buffer.assign(BUFFER_SIZE, 0);
buffer.clear();
int i = TEST_TIMES;
uint64_t total_used = 0;
while (i) {
i--;
steady_clock::time_point time_begin = steady_clock::now();
std::this_thread::sleep_for(microseconds(Interval_Microseconds));
steady_clock::time_point time_end = steady_clock::now();
char tmp[128] = {0};
uint64_t used = duration_cast<microseconds>(time_end - time_begin).count();
snprintf(tmp, 128, "%s Sleep %d us, time used : %lld us\n",
__FUNCTION__, Interval_Microseconds, used);
total_used += used;
buffer += tmp;
}
printf("%s", buffer.c_str());
printf("%s Sleep %d us , avatar %lld us\n\n",
__FUNCTION__, Interval_Microseconds, total_used / TEST_TIMES);
}
结果:
sleep_for 1000us的平均时间也是在1500us左右
三、C++11 sleep_for 加上timeBeginPeriod
代码
void Precision_sleep_for_timeBeginPeriod()
{
// Test for sleep_for and timeBeginPeriod;
std::string buffer;
buffer.assign(BUFFER_SIZE, 0);
buffer.clear();
int i = TEST_TIMES;
uint64_t total_used = 0;
while (i) {
i--;
steady_clock::time_point time_begin = steady_clock::now();
timeBeginPeriod(1);//set Minimum timer resolution.
std::this_thread::sleep_for(microseconds(Interval_Microseconds));
timeEndPeriod(1);
steady_clock::time_point time_end = steady_clock::now();
char tmp[128] = {0};
uint64_t used = duration_cast<microseconds>(time_end - time_begin).count();
snprintf(tmp, 128, "%s Sleep %d us , time used : %lld us\n",
__FUNCTION__, Interval_Microseconds, used);
total_used += used;
buffer += tmp;
}
printf("%s", buffer.c_str());
printf("%s Sleep %d us , avatar %lld us\n\n",
__FUNCTION__,Interval_Microseconds, total_used / TEST_TIMES);
}
结果
无语。。。结果跟不加差不多。。。
四、select的方法
socket 中的select有个超时设置,据说这个超时设置是微秒级的,所以利用这点来测试.
代码
void SleepSelectUS(SOCKET s, int64_t usec)
{
struct timeval tv;
fd_set dummy;
FD_ZERO(&dummy);
FD_SET(s, &dummy);
tv.tv_sec = usec / 1000000L;
tv.tv_usec = usec % 1000000L;
select(0, 0, 0, &dummy, &tv);
DWORD err = GetLastError();
if (err != 0)
printf("Error : %d", err);
}
void Precision_select()
{
std::string buffer;
buffer.assign(BUFFER_SIZE, 0);
buffer.clear();
int i = TEST_TIMES;
uint64_t total_used = 0;
WORD wVersionRequested = MAKEWORD(1, 0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);
SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
while (i) {
i--;
steady_clock::time_point time_begin = steady_clock::now();
SleepSelectUS(s, Interval_Microseconds);
steady_clock::time_point time_end = steady_clock::now();
char tmp[128] = {0};
uint64_t used = duration_cast<microseconds>(time_end - time_begin).count();
snprintf(tmp, 128, "%s Sleep %d us, time used : %lld us\n",__FUNCTION__, Interval_Microseconds, used);
total_used += used;
buffer += tmp;
}
closesocket(s);
printf("%s", buffer.c_str());
printf("%s Sleep %d us, avatar %lld us\n\n", __FUNCTION__, Interval_Microseconds, total_used / TEST_TIMES);
}
结果:
select的精度度稍微有所提高,平均时间为1017us,比上面两个精确一些,基本上可以达到我们的要求。
再看下500us和100us的
500微秒和100微秒的时间差不多,大致可以判断select的精度在500us左右。
五、多媒体时钟轮训的方式
代码
void SleepPerformUS(DWORD usec)
{
LARGE_INTEGER perfCnt, start, now;
QueryPerformanceFrequency(&perfCnt);
QueryPerformanceCounter(&start);
do {
QueryPerformanceCounter((LARGE_INTEGER*)&now);
} while ((now.QuadPart - start.QuadPart) / float(perfCnt.QuadPart) * 1000 * 1000 < usec);
}
void Precision_QueryPerformanceCounter()
{
// Test for select;
std::string buffer;
buffer.assign(BUFFER_SIZE, 0);
buffer.clear();
int i = TEST_TIMES;
uint64_t total_used = 0;
WORD wVersionRequested = MAKEWORD(1, 0);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);
SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
while (i) {
i--;
steady_clock::time_point time_begin = steady_clock::now();
SleepPerformUS(Interval_Microseconds);
steady_clock::time_point time_end = steady_clock::now();
char tmp[128] = {0};
uint64_t used = duration_cast<microseconds>(time_end - time_begin).count();
snprintf(tmp, 128, "%s Sleep 1000 us, time used : %lld us\n", __FUNCTION__, used);
total_used += used;
buffer += tmp;
}
closesocket(s);
printf("%s", buffer.c_str());
printf("%s Sleep 1000 us, avatar %lld us\n\n", __FUNCTION__, total_used / TEST_TIMES);
}
结果
喜出望外~sleep1000us所消耗的时间正好是我们需要的1000us!!!!是不是很心动??
再试试500us和100us的
但是!!!!!别高兴太早!!
从代码里面,我们可以看到这段代码:
do {
QueryPerformanceCounter((LARGE_INTEGER*)&now);
} while ((now.QuadPart - start.QuadPart) / float(perfCnt.QuadPart) * 1000 * 1000 < usec);
这就意味着,这种方式实现的Sleep是不停的在轮训,CPU一直在被占用,我在我的电脑上跑下来,这个轮训会消耗我8%的CPU
我是6核12线程,就意味着是单线程满负荷!
这个方式虽然好用,经不起CPU的消耗,也是没有办法满足我们需要的!
那些在推荐别人使用这种方式的人,不知道自己有没有使用这个代码,如果用在业务上了,我很佩服你们的产品!
六、MsgWaitForMultipleObjectsEx
代码:
void Precision_MsgWaitForMultipleObjectsEx()
{
std::string buffer;
buffer.assign(BUFFER_SIZE, 0);
buffer.clear();
int i = TEST_TIMES;
uint64_t total_used = 0;
while (i) {
i--;
steady_clock::time_point time_begin = steady_clock::now();
MsgWaitForMultipleObjectsEx(0, NULL, Interval_Millisceonds, QS_ALLPOSTMESSAGE,
MWMO_INPUTAVAILABLE);
steady_clock::time_point time_end = steady_clock::now();
char tmp[128] = {0};
uint64_t used = duration_cast<microseconds>(time_end - time_begin).count();
snprintf(tmp, 128, "%s Sleep %d ms, time used : %lld us\n", __FUNCTION__, Interval_Millisceonds, used);
total_used += used;
buffer += tmp;
}
printf("%s", buffer.c_str());
printf("%s Sleep %d us, avatar %lld ms\n\n", __FUNCTION__, Interval_Millisceonds, total_used / TEST_TIMES);
}
结果:
MsgWaitForMultipleObjectsEx测试下来的时间浮动在1200us左右,最低的出现一次1060us,最高的跑到了1400+us,
综上所述,使用select的方式可以让我们的精度有所提高,相对于windows的Sleep和C++11的sleep_for,还是稍微有点提高,
后者时间这么接近,sleep_for的实现,不知道下面是不是就直接调用的Sleep。
另外有个高精度的方式,我没有做测试,从作者给出的时间来看也是很准的,有兴趣的也可以尝试一下
链接地址:高精度/微秒级线程的实现