More accurate alternative to millisecs()

BlitzMax Forums/BlitzMax Programming/More accurate alternative to millisecs()

LeisureSuitLurie(Posted 2005) [#1]
I was going to request it, but in the meantime came up with this (only works on OS X):

I found that millisecs wasn't accurate enough for me, so I present the following.

Add this bit to your Blitz source:

Import "GetTime.c"

Extern
	Function getTime:Double()
End Extern



And place this in a text file named "GetTime.c" in the same folder as your Blitz project:
#include <Carbon/Carbon.h>

double getTime()
{
double t;

t=(double)GetCurrentEventTime();
return t;
}


This returns the time in seconds since System startup. I have no idea how to do this on Windows, but I'm sure someone else does.


Regular K(Posted 2005) [#2]
I dont own BlitzMax

Umm, doesnt millisecs (at least in the other Blitz's) return the milliseconds since system startup?


LeisureSuitLurie(Posted 2005) [#3]
Maybe so, but this is accurate to more than 1/1000th of a second, which is what I needed.


Floyd(Posted 2005) [#4]
So how accurate is it?

I did a quick check with google and didn't find anything specific. There were some suggestions that the resolution is better than one millisecond. But how much better?


RGR(Posted 2005) [#5]
Since you can measure 1.3 and 1.4 microseconds while running a testprogram, its 1/10000000 second.
I'm on windows - so I cannot test it myself and must rely on what I read at iDevGames:

I have exactly what you want...
#include <CoreServices/CoreServices.h>

double GetUpTime(void)
{
AbsoluteTime atime = UpTime();
Nanoseconds nsecs = AbsoluteToNanoseconds(atime);
double time = UnsignedWideToUInt64(nsecs);
return time*1.0E-9;
}; 
This function returns the uptime of the machine in seconds, and on my machine (G4/400) two successive calls measure a delay of 1.3-1.4 microseconds.



Sweenie(Posted 2005) [#6]
On win32 I recommend QueryPerformanceCounter to get the time and QueryPerformanceFrequency to get the resolution of the timer.


SillyPutty(Posted 2005) [#7]
QueryPerfomanceCounter is the highest resolution on Windows.

So yes, tickcount() aint as accurate.


Michael Reitzenstein(Posted 2005) [#8]
Doesn't QueryPerformanceCounter mess up with Intel's SpeedStep and, in the future, AMD's Cool & Quiet?


Sweenie(Posted 2005) [#9]
You are right Michael, I never thought of that.
However, Intel mention something called an Enhanced Timer which make use the QueryPerformance functions but deals with SpeedStep and such.
But I don't know if it could be used in Bmax or not.


Shagwana(Posted 2005) [#10]
This thread on another forum might be of use to you lot.


LeisureSuitLurie(Posted 2005) [#11]
It is more precise than millisecs to be sure. I was finding it difficult to get accurate fps readings when I could only get the time to the 1000th of a second, since my project runs between 300 and 750 fps.


Michael Reitzenstein(Posted 2005) [#12]
It is more precise than millisecs to be sure. I was finding it difficult to get accurate fps readings when I could only get the time to the 1000th of a second, since my project runs between 300 and 750 fps.

Don't count millisecs between frames, count frames per second (or half second, etc). I'll dig up my FPS code:

Const fpsUpdate# = 0.5
Global fpsCount, fpsLastUpdated, fpsLastCount

Function FPS( )
	
	If ( Millisecs( ) - fpsLastUpdated ) > Int( fpsUpdate# * 1000.0 )
		
		fpsLastCount = Float( fpsCount / fpsUpdate# )
		fpsCount = 1
		fpsLastUpdated = MilliSecs( )
		
	Else
	
		fpsCount = fpsCount + 1
		
	EndIf
	
	Return fpsLastCount
	
End Function


With fpsUpdate as default, it counts the number of frames every half second and multiplies by two. This is good because it gives you a precise, to the nearest frame number - it also means the fps counter is MUCH more stable and easier to read than updating every frame.


StuC(Posted 2005) [#13]
Here is some examples of using QueryPerformanceCounter in Windows - wrapped in a little class (on GHz machines, this is nanosecond accuracy):

Framework brl.retro

' initialize the timer internals
TCodeTimer.Initialise()

Print "Timing 1..."

Local myarray:Int[1000]
Local dummy:Int

timer:TCodeTimer = TCodeTimer.Create()

timer.StartWatch()

While timer.WatchTime() < 1000

	timer.StartTimer()
	
	Local ma_len:Int = myarray.length - 1
	For counter = 0 To ma_len
		dummy = myarray[counter]
	Next 
	
	timer.StopTimer()

Wend
	
Print "Using length in local variable"
Print "Iterations: " + timer._iterations
Print "Total Time: " + timer._totaltime
Print "Average time: " + timer._totaltime / Double(timer._iterations)
Print "***************************"
Print 

timer.ResetTimer()

Print "Timing 2..."

timer.StartWatch()

While timer.WatchTime() < 1000

	timer.StartTimer()
	
	For counter = 0 Until myarray.length
		dummy = myarray[counter]
	Next 
	
	timer.StopTimer()

Wend
	
Print "Using length in For/Until"
Print "Iterations: " + timer._iterations
Print "Total Time: " + timer._totaltime
Print "Average time: " + timer._totaltime / Double(timer._iterations)
Print "***************************"
Print 

timer.ResetTimer()

Print "Timing 3..."

timer.StartWatch()

While timer.WatchTime() < 1000

	timer.StartTimer()
	
	For value=EachIn myarray
	Next 
	
	timer.StopTimer()

Wend
	
Print "Using length in For/Until"
Print "Iterations: " + timer._iterations
Print "Total Time: " + timer._totaltime
Print "Average time: " + timer._totaltime / Double(timer._iterations)
Print "***************************"
Print 



' classes
Extern "win32"
	Function QueryPerformanceCounter(count:Long Var)
	Function QueryPerformanceFrequency(freq:Long Var)
End Extern

Type TCodeTimer
	Field _start:Long
	Field _stop:Long
	Field _iterations:Int
	Field _totaltime:Double
	
	Method ResetTimer()
		_start 		= 0
		_stop 		= 0
		_iterations 	= 0
		_totaltime 	= 0
	End Method	
	
	Method StartTimer()
		QueryPerformanceCounter(_start)
	End Method
	
	
	Method StopTimer()
		QueryPerformanceCounter(_stop) 
		_totaltime :+ (Double(_stop - _start) / Double(_freq)) 
		_iterations :+ 1
	End Method
	
	
	''' Stopwatch functions
	Field _watchstart:Double
	
	Method StartWatch()
		self._watchstart = QueryCounter()
	End Method
	
	''' returns the number of milliseconds since calling StartWatch()
	Method WatchTime:Double()
		Return QueryCounter() - _watchstart
	End Method 
	
	''' Globals
	
	''' Initialize should be called before first use, to determine timer class overhead
	Global _overhead:Double	
	Global _freq:Long
	
	Function Create:TCodeTimer()
		Return New TCodeTimer
	End Function
	
	Function Initialise()
		QueryPerformanceFrequency(_freq)
		_freq :/ 1000
	End Function
	
	Function QueryCounter:Double()
		Local counter:Long
		QueryPerformanceCounter(counter)
		Return Double(counter) / _freq
	End Function 

End Type