Windows 7: dépassement de C ++ std :: this_thread :: sleep_for

Notre code est écrit en C ++ 11 (VS2012 / Win 7-64bit). La bibliothèque C ++ fournit une fonction sleep_for que nous utilisons. Nous avons observé que le sleep_for C ++ sleep_for parfois un grand dépassement. En d’autres termes, nous demandons de dormir pendant 15 ms, mais le sumil s’avère être par exemple de 100 ms. Nous le voyons lorsque la charge du système est élevée.

Ma première réaction: “bien sûr, le sumil prend plus de temps” s’il y a beaucoup de charge sur le système et que d’autres threads utilisent le CPU “. Cependant, la chose “drôle” est que si nous remplaçons le sleep_for par un appel Windows “Sleep”, alors nous ne voyons pas ce comportement. J’ai également vu que la fonction sleep_for sous l’eau appelle la méthode Sleep API Window.

La documentation de sleep_for indique:

La fonction bloque le thread appelant au moins l’heure spécifiée par Rel_time . Cette fonction ne lance aucune exception.

Donc, techniquement, la fonction fonctionne. Cependant, nous ne nous attendions pas à voir une différence entre C ++ sleep_for et la fonction normale Sleep(Ex) .

Quelqu’un peut-il expliquer ce comportement?

Il y a un peu de code supplémentaire exécuté si vous utilisez sleep_for vs SleepEx.

Par exemple, l’appel de SleepEx (15) génère l’assembly suivant en mode débogage (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 

Par contre ce code

 const std::chrono::milliseconds duration(15); std::this_thread::sleep_for(duration); 

Génère les éléments suivants:

 ; 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 

Cela appelle à:

 duration PROC ; std::chrono::duration<__int64,std::ratio<1,1000> >::duration<__int64,std::ratio<1,1000> >, 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 

Et appelle dans

  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 

Une certaine conversion se produit:

 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 > >::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(_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 >,__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 

Finalement, la fonction imscope est appelée, la même que celle utilisée par 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 

Vous devez également être conscient que même SleepEx peut ne pas donner 100% de résultats exacts selon la documentation MSDN https://msdn.microsoft.com/en-us/library/windows/desktop/ms686307(v=vs.85).aspx

Cette fonction amène un thread à abandonner le rest de sa tranche de temps et devient impossible à comprendre pour un intervalle basé sur la valeur de dwMilliseconds. L’horloge du système “coche” à un rythme constant. Si dwMilliseconds est inférieur à la résolution de l’horloge système, le thread peut dormir moins longtemps que la durée spécifiée. Si dwMilliseconds est supérieur à un tick mais inférieur à deux, l’attente peut se situer entre un et deux ticks, etc. Pour augmenter la précision de l’intervalle de veille, appelez la fonction timeGetDevCaps pour déterminer la résolution de temporisation minimale prise en charge et la fonction timeBeginPeriod pour définir la résolution du minuteur au minimum. Soyez prudent lorsque vous appelez timeBeginPeriod, car les appels fréquents peuvent affecter de manière significative l’horloge système, la consommation d’énergie du système et le planificateur. Si vous appelez timeBeginPeriod, appelez-le une fois plus tôt dans l’application et assurez-vous d’appeler la fonction timeEndPeriod à la toute fin de l’application.