Be careful with ElapsedMilliseconds() in certain cases

Everything else that doesn't fall into one of the other PB categories.
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Be careful with ElapsedMilliseconds() in certain cases

Post by dige »

I often use ElapsedMilliseconds () for time measurements, especially for timeout checks.
Its very important to know that the value can exceed the $80000000. That means
you'll get negativ results. If you use it in this way (see below), your programm will
hang after some time...

Code: Select all

Procedure ElapsedMilliseconds_()
  ; Simulate a long uptime
  ProcedureReturn ElapsedMilliseconds() + $80000000
EndProcedure

Procedure WaitForTimeOut_1 ()
  Protected TimeOut.i = ElapsedMilliseconds() + 5000
  
  ; Wait 5 Sec. for e.g. a Thread has finished
  
  Repeat
    If Random(10) = 5
      TimeOut = #Null
      ; Thread has finished, Set Timeout to #Null to leave loop now
      Debug "Done"
    Else
      Delay(100)
    EndIf
  Until TimeOut < ElapsedMilliseconds()
  
  Debug "WaitForTimeOut_1 done"
EndProcedure

Procedure WaitForTimeOut_2 ()
  Protected TimeOut.i = ElapsedMilliseconds_() + 5000
  
  ; Wait 5 Sec. for e.g. a Thread has finished

  Repeat
    If Random(10) = 5
      TimeOut = #Null
      ; Thread has finished, Set Timeout to #Null to leave loop now
      Debug "Done"
    Else
      Delay(100)
    EndIf
  Until TimeOut < ElapsedMilliseconds_()
  
  Debug "WaitForTimeOut_2 done"
EndProcedure

; This will work, if your computer have a short uptime
Debug WaitForTimeOut_1()

; Simulate a long uptime, the timeout check does not work in this way
Debug WaitForTimeOut_2()
"Daddy, I'll run faster, then it is not so far..."
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by dige »

@Fred: would it be better to change the ElapsedMilliseconds() result from integer to quad?
"Daddy, I'll run faster, then it is not so far..."
freak
PureBasic Team
PureBasic Team
Posts: 5940
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by freak »

We could also just let the command wrap around to 0 when it reaches the negative range.

btw, it works if you use time differences instead of absolute values. Like this:

Code: Select all

Procedure ElapsedMilliseconds_()
  ; Simulate a long uptime
  ProcedureReturn ElapsedMilliseconds() + $80000000
EndProcedure

Procedure WaitForTimeOut_1 ()
  Protected TimeOut.i = ElapsedMilliseconds() + 5000
 
  ; Wait 5 Sec. for e.g. a Thread has finished
 
  Repeat
    If Random(10) = 5
      TimeOut = #Null
      ; Thread has finished, Set Timeout to #Null to leave loop now
      Debug "Done"
    Else
      Delay(100)
    EndIf
  Until TimeOut < ElapsedMilliseconds()
 
  Debug "WaitForTimeOut_1 done"
EndProcedure

Procedure WaitForTimeOut_2 ()
  Protected Time.i = ElapsedMilliseconds_()
  
  ; Wait 5 Sec. for e.g. a Thread has finished

  Repeat
    If Random(10) = 5
      TimeOut = #Null
      ; Thread has finished, Set Timeout to #Null to leave loop now
      Debug "Done"
    Else
      Delay(100)
    EndIf
  Until ElapsedMilliseconds_() - Time > 5000
 
  Debug "WaitForTimeOut_2 done"
EndProcedure

; This will work, if your computer have a short uptime
Debug WaitForTimeOut_1()

; Simulate a long uptime, the timeout check does not work in this way
Debug WaitForTimeOut_2()
quidquid Latine dictum sit altum videtur
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by Trond »

dige wrote:@Fred: would it be better to change the ElapsedMilliseconds() result from integer to quad?
Where would the quad come from? ElapsedMilliseconds() uses GetTickCount_() on Windows, and GetTickCount_() returns a long.
Thorium
Addict
Addict
Posts: 1305
Joined: Sat Aug 15, 2009 6:59 pm

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by Thorium »

Trond wrote:
dige wrote:@Fred: would it be better to change the ElapsedMilliseconds() result from integer to quad?
Where would the quad come from? ElapsedMilliseconds() uses GetTickCount_() on Windows, and GetTickCount_() returns a long.
From PB, it would just convert the unsignt long to a signt quad and then return it. Just to not get a negative number. It wouldnt count higher.

But the best solution would be a unsingt long. Yes i know thats not gone happen. ^^
User avatar
Michael Vogel
Addict
Addict
Posts: 2797
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by Michael Vogel »

Freaks version works fine in all cases and shows how critical it could be, to make "optimizations"...

Code: Select all

Procedure InitElapsedMiliseconds_()
	Global EM=ElapsedMilliseconds()+1000
EndProcedure
Procedure ElapsedMilliseconds_()
	ProcedureReturn ElapsedMilliseconds() + $80000000-EM
EndProcedure

Procedure WaitForTimeOut_2 ()

	; Wait 5 Sec. for e.g. a Thread has finished
	Protected Time.l = ElapsedMilliseconds_()+5000

	Repeat
		Delay(250)
		Debug Str(ElapsedMilliseconds_())+" > "+Str((Time-ElapsedMilliseconds_())&$FFFF)
		
	;Until ElapsedMilliseconds_()>Time;      DOES NOT WORK !
	Until ElapsedMilliseconds_()-Time>0

	Debug "Exit"
EndProcedure

InitElapsedMiliseconds_()
Debug WaitForTimeOut_2()
I am using this routine for normal, hoping that it uses less CPU cycles, because a comparism with zero behind the UNTIL statement (ElapsedMilliseconds_()-Time>0) may be an easier job than handling non-zero values ElapsedMilliseconds_()-Time>5000).

But don't use ElapsedMilliseconds_()>Time which seems to do the same, it doesn't :wink:
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by dige »

@Freak: I would not change anything internally, except for quad.
If the programmer knows that the value can be negative after
a longer duration, he only needs to adjust its programming style
and avoid comparisons with fixed values.
"Daddy, I'll run faster, then it is not so far..."
Thorium
Addict
Addict
Posts: 1305
Joined: Sat Aug 15, 2009 6:59 pm

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by Thorium »

dige wrote:@Freak: I would not change anything internally, except for quad.
If the programmer knows that the value can be negative after
a longer duration, he only needs to adjust its programming style
and avoid comparisons with fixed values.
Well and thats the problem, "if he knows it". A beginner will typicaly not know that and EllapsedMilliseconds is a command needed by beginners and experienced coders as well.
At least it should be mentioned in the help that it can go to negativ values. But even then, it's a trap.
User avatar
Michael Vogel
Addict
Addict
Posts: 2797
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by Michael Vogel »

dige wrote:@Freak: I would not change anything internally, except for quad.
If the programmer knows that the value can be negative after
a longer duration, he only needs to adjust its programming style
and avoid comparisons with fixed values.
Wouldn't change here anything in the compiler, the PC offers a long value to us, so if a PC is really on for a couple of weeks, an "overflow" can happen: for some it will be already after 3 1/2 week or so when the timer return value will be negative, for others after aaround 7 weeks, when it restarts with zero...
auser
Enthusiast
Enthusiast
Posts: 195
Joined: Wed Sep 06, 2006 6:59 am

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by auser »

Michael Vogel wrote:Freaks version works fine in all cases
I don't agree. Freaks version should fail if you use 64bit version and set the timeout short time before it would flip to negative.

In a nutshell: "Debug(ElapsedMilliseconds() + 2147483647)" differs on 32 bit and 64 bit.

A bit more detailed:
Example - compiled for 64 bit (fail):

Code: Select all

If ElapsedMilliseconds() = 2147483647 - 1000 ; should be one second before it would flip to negative values
  Timeout.i = ElapsedMilliseconds() ; Timeout still got a positive value 
  Repeat
  Until ElapsedMilliseconds() - Timeout > 5000 ; The difference would increase the remaining second before the flip from 0 up to 998, 999, 1000 and afterwards to somewhat like -4294966294, -4294966293, -4294966292, ... (which is still < 5000) - Why? Because we've got 8 bytes now and -4294967294 is a valid value.
Endif
Same compiled for 32 bit would work:

Code: Select all

If ElapsedMilliseconds() = 2147483647 - 1000 ; should be one second before it would flip to negative values
  Timeout.i = ElapsedMilliseconds() ; Timeout still got a positive value 
  Repeat
  Until ElapsedMilliseconds() - Timeout > 5000 ; The difference would increase the remaining second before the flip from 0 up to 998, 999, 1000 and afterwards to somewhat like 1001, 1002 up to 5001 ... (which is greater then 5000) - Why? Because we just got 4 bytes and the same limitation and behaviour then GetTickCount(). So we move to negative (like GetTickCount) if we overflow the limit of 2147483647 as well.
Endif

Possible fix for 64bit (which even works on 32bit) is using long for calculation

Code: Select all

If ElapsedMilliseconds() = 2147483647 - 1000 ; one second before it would flip to negative values
  Timeout.l = ElapsedMilliseconds() + 5000
  Repeat
    Checktimout.l = ElapsedMilliseconds() - Timeout ; would even stay inside the 4 byte range - so flips to negative/positive as well
  Until Checktimout > 0; Why does this work? Because we got our limitation back again in the line above. So if the value goes to negative we would follow via "long" again that even would turn to negative values if we feed it too much (which is good in that case).
Endif
Anyway this would not fix issues if you set timeouts greater then a few weeks (but in that case Elapsedmilliseconds() would be the wrong function anyway I guess).
MachineCode
Addict
Addict
Posts: 1482
Joined: Tue Feb 22, 2011 1:16 pm

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by MachineCode »

Perhap's PB's post in this thread (exact same topic) may be of interest -> http://www.purebasic.fr/english/viewtop ... 13&t=35650
Microsoft Visual Basic only lasted 7 short years: 1991 to 1998.
PureBasic: Born in 1998 and still going strong to this very day!
auser
Enthusiast
Enthusiast
Posts: 195
Joined: Wed Sep 06, 2006 6:59 am

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by auser »

MachineCode wrote:Perhap's PB's post in this thread (exact same topic) may be of interest -> http://www.purebasic.fr/english/viewtop ... 13&t=35650
You are right the topic seems similar but in my opinion it's a bit hard to find the right sollution there ("msDiff.l = msNew - msOld" - figured out by Kaeru Gaman). I run into that issue myself already in the past where a server-process hang up after a few weeks. Then I found this thread in the past and I thought long time that "freaks sollution" would work and started using ElapsedMilliseconds with differences all the time. But that's not a bullet-proof solution again (on 64 bit). Using just "<" or ">" is a trap because the value moves to negative. Using just "<" or ">" AND differences is a trap as well - on 64bit (because int64 "-2000000000 - 2000000000" is just -4000000000 there and that's far away from > 5000). So in my opinion the only sollution that really works everywhere is using long (4 byte size) - or use some different API function (e.g. gettimeofday() in linux or some GetTickCount64() on windows and using quads).
MachineCode
Addict
Addict
Posts: 1482
Joined: Tue Feb 22, 2011 1:16 pm

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by MachineCode »

auser wrote:it's a bit hard to find the right sollution there
I meant his post near the end of that thread, which is 100% foolproof, even on 64bit machines.
Microsoft Visual Basic only lasted 7 short years: 1991 to 1998.
PureBasic: Born in 1998 and still going strong to this very day!
auser
Enthusiast
Enthusiast
Posts: 195
Joined: Wed Sep 06, 2006 6:59 am

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by auser »

MachineCode wrote:
auser wrote:it's a bit hard to find the right sollution there
I meant his post near the end of that thread, which is 100% foolproof, even on 64bit machines.
His? Who is he? Kaeru Gaman post is above the mid of the thread and no real world example anyway. So dunno what you mean. The last post did not even use some PB function and is not using "long" at all. As I wrote "msDiff.l = msNew - msOld" is right (because it's using 4 bytes long as well). So it seems even Kaeru Gaman figured out the right sollution. However - searching the forum even dropped me here in the past. And the sollution posted here so far fixed my issues just on 32bit but is again a trap for 64bit. And since freak is no nobody in this forum you might just think you could take it as it is. I would stress again that int would not work as difference that way on 64bit, even a difference using one line like "Elapsedmilliseconds() - something" (where it does not matter if "something" is int or long) does NOT work (it would return a correct but fatal 8 byte value on 64bit which is too high to imitate the overflow turnaround behavior as expected.

I miss a note in the thread you linked that it's working just because of long (4 bytes). So some might think pretty fast he could even use "diff" or "diff.i" or just use the result of the difference in one line using "Elapsedmilliseconds() - something" - (all three would fail on 64bit).
MachineCode
Addict
Addict
Posts: 1482
Joined: Tue Feb 22, 2011 1:16 pm

Re: Be careful with ElapsedMilliseconds() in certain cases

Post by MachineCode »

What? A real-world solution is posted in the other thread and uses quads for the difference, which is 64bit-safe.
Microsoft Visual Basic only lasted 7 short years: 1991 to 1998.
PureBasic: Born in 1998 and still going strong to this very day!
Post Reply