Page 1 of 2
Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jun 16, 2010 10:02 am
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()
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jun 16, 2010 12:43 pm
by dige
@Fred: would it be better to change the ElapsedMilliseconds() result from integer to quad?
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jun 16, 2010 1:00 pm
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()
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jun 16, 2010 1:40 pm
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.
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jun 16, 2010 4:22 pm
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. ^^
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Thu Jun 17, 2010 6:33 am
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

Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Thu Jun 17, 2010 8:28 am
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.
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Thu Jun 17, 2010 10:20 am
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.
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Thu Jun 17, 2010 2:39 pm
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...
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jul 11, 2012 10:17 am
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).
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jul 11, 2012 12:10 pm
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
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jul 11, 2012 1:32 pm
by auser
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).
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jul 11, 2012 2:19 pm
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.
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jul 11, 2012 2:53 pm
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).
Re: Be careful with ElapsedMilliseconds() in certain cases
Posted: Wed Jul 11, 2012 3:01 pm
by MachineCode
What? A real-world solution is posted in the other thread and uses quads for the difference, which is 64bit-safe.