我们的代码是用C ++ 11(VS2012 / Win 7-64bit)编写的。 C ++库提供了一个我们使用的sleep_for
函数。 我们观察到C ++ sleep_for
有时会显示一个大的超调量。 换句话说,我们要求睡15毫秒,但睡眠结果是例如100毫秒。 我们在系统负载很高的时候看到这个。
我的第一个反应是:“当然,睡眠”需要更长时间“如果系统上有很多负载,而其他线程正在使用CPU”。 然而,“有趣”的是,如果我们用Windows API“睡眠”调用replacesleep_for
,那么我们就不会看到这种行为。 我还看到, sleep_for
函数调用Window API Sleep
方法。
sleep_for的文档状态:
该函数至less在Rel_time指定的时间内阻塞调用线程。 这个函数不会抛出任何exception。
所以在技术上这个function正在起作用。 然而,我们并不期望看到C ++ sleep_for
和普通的Sleep(Ex)
函数之间的区别。
有人可以解释这种行为吗?
如果使用sleep_for和SleepEx,还会执行相当多的代码。
例如调用SleepEx(15)以调试模式(Visual Studio 2015)生成以下程序集:
; 9 : SleepEx(15, false); mov esi, esp push 0 push 15 ; 0000000fH call DWORD PTR __imp__SleepEx@8 cmp esi, esp call __RTC_CheckEsp
相比之下这个代码
const std::chrono::milliseconds duration(15); std::this_thread::sleep_for(duration);
生成以下内容:
; 9 : std::this_thread::sleep_for(std::chrono::milliseconds(15)); mov DWORD PTR $T1[ebp], 15 ; 0000000fH lea eax, DWORD PTR $T1[ebp] push eax lea ecx, DWORD PTR $T2[ebp] call duration push eax call sleep_for add esp, 4
这要求:
duration PROC ; std::chrono::duration<__int64,std::ratio<1,1000> >::duration<__int64,std::ratio<1,1000> ><int,void>, COMDAT ; _this$ = ecx ; 113 : { // construct from representation push ebp mov ebp, esp sub esp, 204 ; 000000ccH push ebx push esi push edi push ecx lea edi, DWORD PTR [ebp-204] mov ecx, 51 ; 00000033H mov eax, -858993460 ; ccccccccH rep stosd pop ecx mov DWORD PTR _this$[ebp], ecx ; 112 : : _MyRep(static_cast<_Rep>(_Val)) mov eax, DWORD PTR __Val$[ebp] mov eax, DWORD PTR [eax] cdq mov ecx, DWORD PTR _this$[ebp] mov DWORD PTR [ecx], eax mov DWORD PTR [ecx+4], edx ; 114 : } mov eax, DWORD PTR _this$[ebp] pop edi pop esi pop ebx mov esp, ebp pop ebp ret 4 duration ENDP
并呼吁
sleep_for PROC ; std::this_thread::sleep_for<__int64,std::ratio<1,1000> >, COMDAT ; 151 : { // sleep for duration push ebp mov ebp, esp sub esp, 268 ; 0000010cH push ebx push esi push edi lea edi, DWORD PTR [ebp-268] mov ecx, 67 ; 00000043H mov eax, -858993460 ; ccccccccH rep stosd mov eax, DWORD PTR ___security_cookie xor eax, ebp mov DWORD PTR __$ArrayPad$[ebp], eax ; 152 : stdext::threads::xtime _Tgt = _To_xtime(_Rel_time); mov eax, DWORD PTR __Rel_time$[ebp] push eax lea ecx, DWORD PTR $T1[ebp] push ecx call to_xtime add esp, 8 mov edx, DWORD PTR [eax] mov DWORD PTR $T2[ebp], edx mov ecx, DWORD PTR [eax+4] mov DWORD PTR $T2[ebp+4], ecx mov edx, DWORD PTR [eax+8] mov DWORD PTR $T2[ebp+8], edx mov eax, DWORD PTR [eax+12] mov DWORD PTR $T2[ebp+12], eax mov ecx, DWORD PTR $T2[ebp] mov DWORD PTR __Tgt$[ebp], ecx mov edx, DWORD PTR $T2[ebp+4] mov DWORD PTR __Tgt$[ebp+4], edx mov eax, DWORD PTR $T2[ebp+8] mov DWORD PTR __Tgt$[ebp+8], eax mov ecx, DWORD PTR $T2[ebp+12] mov DWORD PTR __Tgt$[ebp+12], ecx ; 153 : sleep_until(&_Tgt); lea eax, DWORD PTR __Tgt$[ebp] push eax call sleep_until add esp, 4 ; 154 : } push edx mov ecx, ebp push eax lea edx, DWORD PTR $LN5@sleep_for call @_RTC_CheckStackVars@8 pop eax pop edx pop edi pop esi pop ebx mov ecx, DWORD PTR __$ArrayPad$[ebp] xor ecx, ebp call @__security_check_cookie@4 add esp, 268 ; 0000010cH cmp ebp, esp call __RTC_CheckEsp mov esp, ebp pop ebp ret 0 npad 3 $LN5@sleep_for: DD 1 DD $LN4@sleep_for $LN4@sleep_for: DD -24 ; ffffffe8H DD 16 ; 00000010H DD $LN3@sleep_for $LN3@sleep_for: DB 95 ; 0000005fH DB 84 ; 00000054H DB 103 ; 00000067H DB 116 ; 00000074H DB 0 sleep_for ENDP
一些转换发生:
to_xtime PROC ; std::_To_xtime<__int64,std::ratio<1,1000> >, COMDAT ; 758 : { // convert duration to xtime push ebp mov ebp, esp sub esp, 348 ; 0000015cH push ebx push esi push edi lea edi, DWORD PTR [ebp-348] mov ecx, 87 ; 00000057H mov eax, -858993460 ; ccccccccH rep stosd mov eax, DWORD PTR ___security_cookie xor eax, ebp mov DWORD PTR __$ArrayPad$[ebp], eax ; 759 : xtime _Xt; ; 760 : if (_Rel_time <= chrono::duration<_Rep, _Period>::zero()) lea eax, DWORD PTR $T7[ebp] push eax call duration_zero ; std::chrono::duration<__int64,std::ratio<1,1000> >::zero add esp, 4 push eax mov ecx, DWORD PTR __Rel_time$[ebp] push ecx call chronos_operator ; std::chrono::operator<=<__int64,std::ratio<1,1000>,__int64,std::ratio<1,1000> > add esp, 8 movzx edx, al test edx, edx je SHORT $LN2@To_xtime ; 761 : { // negative or zero relative time, return zero ; 762 : _Xt.sec = 0; xorps xmm0, xmm0 movlpd QWORD PTR __Xt$[ebp], xmm0 ; 763 : _Xt.nsec = 0; mov DWORD PTR __Xt$[ebp+8], 0 ; 764 : } ; 765 : else jmp $LN3@To_xtime $LN2@To_xtime: ; 766 : { // positive relative time, convert ; 767 : chrono::nanoseconds _T0 = ; 768 : chrono::system_clock::now().time_since_epoch(); lea eax, DWORD PTR $T5[ebp] push eax lea ecx, DWORD PTR $T6[ebp] push ecx call system_clock_now ; std::chrono::system_clock::now add esp, 4 mov ecx, eax call time_since_ephoch ; std::chrono::time_point<std::chrono::system_clock,std::chrono::duration<__int64,std::ratio<1,10000000> > >::time_since_epoch push eax lea ecx, DWORD PTR __T0$8[ebp] call duration ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::duration<__int64,std::ratio<1,1000000000> ><__int64,std::ratio<1,10000000>,void> ; 769 : _T0 += _Rel_time; mov eax, DWORD PTR __Rel_time$[ebp] push eax lea ecx, DWORD PTR $T4[ebp] call duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::duration<__int64,std::ratio<1,1000000000> ><__int64,std::ratio<1,1000>,void> lea ecx, DWORD PTR $T4[ebp] push ecx lea ecx, DWORD PTR __T0$8[ebp] call duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::operator+= ; 770 : _Xt.sec = chrono::duration_cast<chrono::seconds>(_T0).count(); lea eax, DWORD PTR __T0$8[ebp] push eax lea ecx, DWORD PTR $T3[ebp] push ecx call duration_cast ; std::chrono::duration_cast<std::chrono::duration<__int64,std::ratio<1,1> >,__int64,std::ratio<1,1000000000> > add esp, 8 mov ecx, eax call duration_count ; std::chrono::duration<__int64,std::ratio<1,1> >::count mov DWORD PTR __Xt$[ebp], eax mov DWORD PTR __Xt$[ebp+4], edx ; 771 : _T0 -= chrono::seconds(_Xt.sec); lea eax, DWORD PTR __Xt$[ebp] push eax lea ecx, DWORD PTR $T1[ebp] call duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1> >::duration<__int64,std::ratio<1,1> ><__int64,void> push eax lea ecx, DWORD PTR $T2[ebp] call duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::duration<__int64,std::ratio<1,1000000000> ><__int64,std::ratio<1,1>,void> lea ecx, DWORD PTR $T2[ebp] push ecx lea ecx, DWORD PTR __T0$8[ebp] call duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::operator-= ; 772 : _Xt.nsec = (long)_T0.count(); lea ecx, DWORD PTR __T0$8[ebp] call duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::count mov DWORD PTR __Xt$[ebp+8], eax $LN3@To_xtime: ; 773 : } ; 774 : return (_Xt); mov eax, DWORD PTR $T9[ebp] mov ecx, DWORD PTR __Xt$[ebp] mov DWORD PTR [eax], ecx mov edx, DWORD PTR __Xt$[ebp+4] mov DWORD PTR [eax+4], edx mov ecx, DWORD PTR __Xt$[ebp+8] mov DWORD PTR [eax+8], ecx mov edx, DWORD PTR __Xt$[ebp+12] mov DWORD PTR [eax+12], edx mov eax, DWORD PTR $T9[ebp] ; 775 : } push edx mov ecx, ebp push eax lea edx, DWORD PTR $LN8@To_xtime call @_RTC_CheckStackVars@8 pop eax pop edx pop edi pop esi pop ebx mov ecx, DWORD PTR __$ArrayPad$[ebp] xor ecx, ebp call @__security_check_cookie@4 add esp, 348 ; 0000015cH cmp ebp, esp call __RTC_CheckEsp mov esp, ebp pop ebp ret 0 $LN8@To_xtime: DD 2 DD $LN7@To_xtime $LN7@To_xtime: DD -24 ; ffffffe8H DD 16 ; 00000010H DD $LN5@To_xtime DD -40 ; ffffffd8H DD 8 DD $LN6@To_xtime $LN6@To_xtime: DB 95 ; 0000005fH DB 84 ; 00000054H DB 48 ; 00000030H DB 0 $LN5@To_xtime: DB 95 ; 0000005fH DB 88 ; 00000058H DB 116 ; 00000074H DB 0 to_xtime ENDP
最终导入的函数被调用,与SleepEx使用的一样。
sleep_until PROC ; std::this_thread::sleep_until, COMDAT ; 131 : { // sleep until _Abs_time push ebp mov ebp, esp sub esp, 192 ; 000000c0H push ebx push esi push edi lea edi, DWORD PTR [ebp-192] mov ecx, 48 ; 00000030H mov eax, -858993460 ; ccccccccH rep stosd ; 132 : _Thrd_sleep(_Abs_time); mov esi, esp mov eax, DWORD PTR __Abs_time$[ebp] push eax call DWORD PTR __imp___Thrd_sleep add esp, 4 cmp esi, esp call __RTC_CheckEsp ; 133 : } pop edi pop esi pop ebx add esp, 192 ; 000000c0H cmp ebp, esp call __RTC_CheckEsp mov esp, ebp pop ebp ret 0 sleep_until ENDP
您也应该知道即使SleepEx可能不会按照MSDN文档100%准确的结果https://msdn.microsoft.com/en-us/library/windows/desktop/ms686307(v=vs.85).aspx
该函数使线程放弃其时间片的剩余部分,并在基于dwMilliseconds值的时间间隔内变为不可运行。 系统时钟以固定速率“滴答”。 如果dwMilliseconds小于系统时钟的分辨率,线程可能会睡眠的时间少于指定的时间长度。 如果dwMilliseconds大于一个tick但小于两个,则等待可以是一个到两个tick之间的任何地方,依此类推。 要提高睡眠间隔的准确性,请调用timeGetDevCaps函数以确定支持的最小定时器分辨率和timeBeginPeriod函数,以将定时器分辨率设置为最小值。 调用timeBeginPeriod时要小心,因为频繁的调用会显着影响系统时钟,系统电源使用情况和调度程序。 如果您调用timeBeginPeriod,请在应用程序的早期一次调用它,并确保在应用程序的最后调用timeEndPeriod函数。