c + + - Hvad sker der når GetTickCount () wraps?

Indlæg af Hanne Mølgaard Plasc

Problem



Hvis en tråd gør noget som dette:


const DWORD interval = 20000;
DWORD ticks = GetTickCount();
while(true)
{
    DoTasksThatTakeVariableTime();

    if( GetTickCount() - ticks > interval )
    {
        DoIntervalTasks();
        ticks = GetTickCount();
    }
}


Til sidst kommer flåter til at pakke ind, når værdien ikke passer i en DWORD.


Jeg har diskuteret dette med en kollega. En af os mener, at koden stadig vil opføre sig 'pænt', når ombrydningen opstår, fordi subtraktionsoperationen også vil vikle. Den anden af ​​os mener, at det ikke altid virker, især hvis intervallet er stort.


Hvem har ret, og hvorfor?


Tak.

Bedste reference


Fra dokumenterne: [35]



  Den forløbet tid er gemt som en DWORD
  værdi. Derfor vil tiden vikle
  rundt til nul, hvis systemet køres
  kontinuerligt i 49,7 dage. At undgå
  Dette problem, brug GetTickCount64.
  Ellers skal du kontrollere for et overløb
  tilstand ved sammenligning af tider.



DWORD er dog usigneret - så du skal være okay. 0 - 'meget stort nummer'='lille nummer' (forudsat at du ikke har nogen overløbskontrol aktiv, selvfølgelig). Jeg havde en tidligere redigering, som foreslog, at du fik et negativt tal, men det var før jeg tog fat i Vær opmærksom på, at DWORD er usigneret.


Du har stadig et problem, hvis operationen tager lige under 49,7 dage. Det kan ikke være et problem for dig;)


En måde at teste på ville være at udstikke GetTickCount() metoden, så du kunne skrive enhedsprøver, hvor du eksplicit gør det ombrydende. Så igen, hvis du virkelig tvivler på den aritmetiske del, kan du nemt skrive enhedsprøver for det :) Faktisk er det forhold, at tallet kommer fra et systemur, stort set irrelevant, så længe du kender adfærden, når den wraps - og det er angivet i dokumentationen.

Andre referencer 1


Intet der sker, så længe:



  • Du trækker DWORD s, i stedet for at konvertere til en anden type først.

  • Intet, du forsøger at tage tid, tager længere tid end 49,7 dage.



Dette skyldes, at usigneret aritmetisk overløb er veldefineret i C, og indpakningsadfærd gør præcis det, vi ønsker.


DWORD t1, t2;
DWORD difference;

t1 = GetTickCount();
DoSomethingTimeConsuming();
t2 = GetTickCount();


t2 - t1 vil producere den korrekte værdi, selvom GetTickCount ombrydes. Du må bare konvertere t2 og t1 til en anden type (fx int eller double) før du foretager subtraktionen.


Dette virker ikke, hvis programmeringssproget behandler overløb som en fejl. Det vandt også ikke, hvis DoSomethingTimeConsuming() tager længere tid end 49,7 dage. Du kan ikke fortælle bare ved at se på t2 og t1 hvor mange gange GetTickCount indpakket rundt, desværre.





Lad os starte med det sædvanlige tilfælde, hvor der ikke kommer noget omslag på spil:


t1 = 13487231
t2 = 13492843


Her t2 - t1 = 5612, hvilket betyder at operationen tog omkring fem sekunder.


Overvej nu en operation, der tager kort tid, men hvor GetTickCount viklede rundt:


t1 = 4294967173
t2 = 1111


Operationen tog 1234ms, men timeren blev viklet rundt, og 1111 - 4294967173 er den falske værdi af -4294966062. Hvad nogensinde vil vi gøre?


Nå, modulo 2 32 , også resultatet af subtraktion ombrydes:


(DWORD)-4294966062 == (DWORD)1234


Endelig overveje kanten, hvor en operation tager næsten 2 32 millisekunder, men ikke helt:


t1 = 2339189280
t2 = 2339167207


Her indpakket GetTickCount og kom lige tilbage om hvor den var.


Nu t2 - t1 giver den falske værdi af 4294945223. Det er fordi det er den tid, operationen faktisk tog!


Generelt:


(base + offset) - base ≡ offset mod 2^32

Andre referencer 2


Hvis du vil teste hvad der sker, når GetTickCount() wraps, kan du aktivere Application Verifier s TimeRollOver test.


Fra Brug af applikationsverifikator inden for din Software Development Lifecycle: [36]



   TimeRollOver tvinger GetTickCount og TimeGetTime API'erne til at rulle hurtigere over, end de normalt ville. Dette gør det muligt for applikationer at teste deres håndtering af tidsoverdragelse lettere.


Andre referencer 3


Jeg vil foreslå at beregne den faktiske forløbet periode mellem de to flåter, ikke stole på kompilatoren til at håndtere det for dig:


const DWORD interval = 20000;

#define TICKS\_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+1+(cur))

DWORD ticks = GetTickCount();
while(true)
{
    DoTasksThatTakeVariableTime();

    DWORD curticks = GetTickCount();
    if( TICKS\_DIFF(ticks, curticks) > interval )
    {
        DoIntervalTasks();
        ticks = GetTickCount();
    }
}

Andre referencer 4


Jeg løb for nylig på dette problem. Koden jeg arbejdede på brugte GetTickCount () i en masse steder for at afgøre, om programmet brugte for meget tid på en bestemt opgave, og i så fald ville det afbryde denne opgave og omplanlægge den til senere udførelse. Hvad der ville ske, er, at hvis GetTickCount () indpakket i løbet af en af ​​måleperioderne, ville det medføre, at koden slår ud tidligt. Dette var en tjeneste, der løber konstant, så hver 49 dage ville det have en lille hik.


Jeg fik det ved at skrive en timerklasse, der brugte GetTickCount () internt, men opdagede, når værdien indpakket og kompenseret for den.

Andre referencer 5


Et stort bang opstår.
Åh undskyld, en big bang wraps.

Andre referencer 6


Du kan teste det;) - Jeg har en simpel testapplikation her, som lancerer en app og krog GetTickCount() i den, så du kan styre den fra GUI i testappen. Jeg skrev det som stubbende ud GetTickCount() opkaldene i nogle apps er ikke så nemt.


TickShifter er gratis og er tilgængelig her: http://www.lenholgate.com/blog/2006/04/tickshifter-v02.html [37]


Jeg skrev det, mens jeg skrev en række artikler om testdrevet udvikling, som brugte nogle kode, der brugte GetTickCount() på en brudt måde.


Artikler er tilgængelige her, hvis du er interesseret: http://www.lenholgate.com/blog/2004/05/practical-testing.html[38]


Men i sammendrag vil din kode arbejde ...

Andre referencer 7


Denne artikel hjalp mig, men jeg tror, ​​der er en fejl i:


#define TICKS\_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur))


Da jeg testede dette på omløbspunktet, fandt jeg det var slukket med 1.


Hvad der fungerede for mig var:


define TICKS\_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur)+1)

Andre referencer 8


Sikkert Du skal håndtere dette kryds wrap problem.


Linux-kerne håndterer sådan kryds wrap problem med følgende trick:



  #definerede tid\_after (a, b) ((lang) (b) - (lang) (a) <0))



Ideen er udstedt usigneret til underskrevet og sammenligner deres værdi, så kun hvis | a-b | < 2 ^ 30, så har wrap ikke indflydelse på resultatet.


Du kan prøve med dette trick og få at lære, hvorfor det virker.


Da DWORD også er usigneret int, bør dette trick også fungere for Windows.


Så din kode kunne være sth som:



  const DWORD interval=20000;

  
  DWORD ticks=GetTickCount () + interval;

  
  mens (sandt) {


DoTasksThatTakeVariableTime();

if(time\_after(ticks, GetTickCount())
{
    DoIntervalTasks();
    ticks = GetTickCount() + interval;
} 

  
  }



Kun hvis interval mindre end 0x2 ^ 30, virker det.