Skip to main content
GameDev.net gamedev.net
🔒 Locked

[.net] Timing Is Everything

Started by Rob Loach Feb 7, 2006 at 10:54 PM 20 replies 10.4k views
Original Post
Rob Loach
Rob Loach
I'm working on a new project using OpenGL through .NET via Tao.OpenGl and Tao.FreeGLUT and have come to a problem when it comes to getting the tick time. Does anyone know of a good way to get the tick time in .NET or GLUT? Although MSDN recommends using the QueryPerformanceCounter, it is dependant on Kernal32 which requires the client computer to be running on Windows. One of the goals of this project is to have it be able to compile on Mono for compatibility with Mac/Linux. GLUT has a timer that calls back after a given number of milliseconds, but I need a timer system to count how long an opperation took....
Rob Loach [Website] [Projects] [
Krisc
Krisc
Hey Rob!

I am using the .net Stopwatch class to time stuff in my project. However this won't help the Mac situation. I presume the Unix/Linux os's have their own method of querying a timer.
AlphaGremlin
AlphaGremlin
Have you looked at DateTime.Now.Ticks?
AlphaGremlin
RipTorn
RipTorn
Environment.TickCount gives the millisecond value that QueryPerformanceCounter would return (although as int, not float), so you loose a tiny bit of precision but it hardly matters. It also seems to not be afflicted by QueryPerformanceCounter problems on multi-core cpus like you can get on amd systems with incorrect drivers.
Thevenin
Thevenin
I thought Environment.TickCount was a wrapper around GetTickCount()...

In this case, it has a pretty bad accuracy precision (12ms).
cody
cody
yes, Environment.TickCount has a bad precision.
i use Tao.Sdl for timing stuff, you could even use it with glut, but i think you should use sdl instead anyway.
nagromo
nagromo
From my tests, Environment.TickCount has poor accuracy (~16 ms) on Windows but excellent accuracy (~2 ms) on Linux. You could write a wrapper class that will try to use QueryPerformanceCounter, and if it fails, use Environment.TickCount, which works just fine on Linux. I haven't tested it on Macs, though.
Rob Loach
Rob Loach
Quote:
Original post by nagromo
From my tests, Environment.TickCount has poor accuracy (~16 ms) on Windows but excellent accuracy (~2 ms) on Linux. You could write a wrapper class that will try to use QueryPerformanceCounter, and if it fails, use Environment.TickCount, which works just fine on Linux. I haven't tested it on Macs, though.
Another demonstration of how open-source can strive over closed-source [wink].

That's pretty unfortunate though. I could write up a class that uses the QueryPerformanceCounter if the compiling computer is on Windows and uses Environment.TickCount otherwise... Any comments?
Rob Loach [Website] [Projects] [
benryves
benryves
I use DateTime.Now over Environment.TickCount.
Environment.TickCount can only be guaranteed to have a 500ms resolution, which is a bit rubbish! (DateTime.Now should have 10ms). Clearly, in practice, the resolution is a lot finer in any case.
[Website] [+++ Divide By Cucumber Error. Please Reinstall Universe And Reboot +++]
Krisc
Krisc
Wait wait... Isn't the tick count 1:1 to how many ticks the CPU ran... so if you do tick_count/frequency you can figure out how much time was spent with a precision directly related to the frequency of the computer?

This is how I am doing timing in Dx5D... and it has improved precision/accuracy of the timing greatly over using Stopwatch.ElapsedMilliseconds and the DLL's GetTickCount()...

But then again I am using Stopwatch.ElapsedTicks and not Environment.TickCount.
davepermen
davepermen
not on my system, as it ranges from 800mhz to 2ghz all the time.
If that's not the help you're after then you're going to have to explain the problem better than what you have. - joanusdmentia
My Page davepermen.net | My Music on Bandcamp and on Soundcloud
Rob Loach
Rob Loach
Quote:
Original post by Krisc
But then again I am using Stopwatch.ElapsedTicks and not Environment.TickCount.
The sad thing is that Stopwatch requires .NET 2.0 and its not yet implemented into Mono.
Rob Loach [Website] [Projects] [
RipTorn
RipTorn
yeah you guys are right about Environment.TickCount. It is ~15ms. Strange. I did test it a while back and I was sure it was higher but I guess I was wrong.

Anywho, this has prompted me to change the way I'm doing things:

this is effectivly what I'm doing now:

...[System.Security.SuppressUnmanagedCodeSecurity][DllImport("winmm.dll", ExactSpelling = true, CharSet = CharSet.Auto)]private static extern int timeGetTime();private bool useTimeGetTime = Environment.OSVersion.Platform != PlatformID.Unix;public int TickCount{	get	{		if (useTimeGetTime)			return timeGetTime();		else			return Environment.TickCount;	}}...



I've noticed a very slight improvment in the overall smoothness of output, which is a bonus.

I am still unwilling to use performance counters as I've encountered issues with them in the past causing hitching. And timeGetTime is 1ms accurate so it's good enough.
Seroja
Seroja
I ran some little NVidia program that tested timers. Here it is:
http://developer.nvidia.com/object/timer_function_performance.html

It told me that on my computer GetTickCount worked best:
Report file for timing the various timers.*** Key number is the avg time.    The smaller this number, the faster the timer.QueryPerformanceFrequency() freq  = 0  1193182 method 0:  QueryPerfCntr..()  100 times  tot:   0 763  avg:   7.630000  avg time:			 6.39467e-006method 0:  QueryPerfCntr..()  500 times  tot:   0 4112  avg:   8.224000  avg time:			 6.89249e-006method 0:  QueryPerfCntr..()  1000 times  tot:   0 7423  avg:   7.423000  avg time:			 6.22118e-006method 0:  QueryPerfCntr..()  10000 times  tot:   0 78671  avg:   7.867100  avg time:			 6.59338e-006method 1:  GetTickCount()  100 times  tot:   0 9  avg:   0.090000  avg time:			 7.54286e-008method 1:  GetTickCount()  500 times  tot:   0 16  avg:   0.032000  avg time:			 2.6819e-008method 1:  GetTickCount()  1000 times  tot:   0 25  avg:   0.025000  avg time:			 2.09524e-008method 1:  GetTickCount()  10000 times  tot:   0 186  avg:   0.018600  avg time:			 1.55886e-008method 2:  TimeGetTime()  100 times  tot:   0 27  avg:   0.270000  avg time:			 2.26286e-007method 2:  TimeGetTime()  500 times  tot:   0 104  avg:   0.208000  avg time:			 1.74324e-007method 2:  TimeGetTime()  1000 times  tot:   0 191  avg:   0.191000  avg time:			 1.60076e-007method 2:  TimeGetTime()  10000 times  tot:   0 1853  avg:   0.185300  avg time:			 1.55299e-007method 3:  Pentium internal high-freq cntr()  100 times  tot:   0 13  avg:   0.130000  avg time:			 1.08952e-007method 3:  Pentium internal high-freq cntr()  500 times  tot:   0 36  avg:   0.072000  avg time:			 6.03428e-008method 3:  Pentium internal high-freq cntr()  1000 times  tot:   0 65  avg:   0.065000  avg time:			 5.44762e-008method 3:  Pentium internal high-freq cntr()  10000 times  tot:   0 594  avg:   0.059400  avg time:			 4.97828e-008


Or did I misinterpret the results?
DaBono
DaBono
Quote:
Original post by SerojaIt told me that on my computer GetTickCount worked best.
Or did I misinterpret the results?

Well, it tells you that the calls to GetTickCount() take the least time. It, however, tells you nothing about the accuracy of the value it returned.
Seroja
Seroja
Well if a call to the timer takes longer, wouldn't that lower its accuracy?
Bob Janova
Bob Janova
Yes, but the reverse is not necessarily true. It could run very quickly but give you the wrong number.
Seroja
Seroja
Guess you're right.

Googling a bit, I found out that GetTickCount has a rather low accuracy (~50ms). However, it is a stable solution for background tasks polling (very low overhead).
Other solutions are timeGetTime (multimedia timer) which should have a 1ms accuracy, but that only on Win9x :(
If used in combination timeBeginPeriod and timeEndPeriod functions it could give better accuracy, though I doubt it will reach the 1ms accuracy on WinNT.
Sleep is very inaccurate, especially if there are more than 1 thread running.
PerformanceCounter should be the best solution (good accuracy < 1ms, acceptable overhead), but unfortunately some processors have trouble with it (e.g. having totally wrong values). That would be best if you assume that it works correctly.

I wonder what would happen if I called timeBeginPeriod(1) on start of my app, and timeEndPeriod(1) on exit, and replace all GetTickCounts with timeGetTime calls.
nagromo
nagromo
Quote:
Original post by Rob Loach
Quote:
Original post by nagromo
From my tests, Environment.TickCount has poor accuracy (~16 ms) on Windows but excellent accuracy (~2 ms) on Linux. You could write a wrapper class that will try to use QueryPerformanceCounter, and if it fails, use Environment.TickCount, which works just fine on Linux. I haven't tested it on Macs, though.
Another demonstration of how open-source can strive over closed-source [wink].

That's pretty unfortunate though. I could write up a class that uses the QueryPerformanceCounter if the compiling computer is on Windows and uses Environment.TickCount otherwise... Any comments?


Sorry for not replying sooner, but it's a lot easier than that.

I wrote a single timer class that chooses which to use at runtime. In the init function, it calls QueryPerformanceFrequency in a try block; if an exception is thrown it sets a boolean value to tell it to use Environment.TickCount after that. You can have code that calls non-existant dll functions; it'll just throw an exception when you try to call that function.

I'll upload the source here in a bit, after I clean it up.

/* * Timer.cs - a cross-platform hi-res timer * Version 0.5, 16 May 2005 * * Copyright (C) 2004-2005 Morgan LaMoore * * This software is provided 'as-is', without any express or implied * warranty.  In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not *    claim that you wrote the original software. If you use this software *    in a product, an acknowledgment in the product documentation would be *    appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be *    misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. *  * Morgan LaMoore morganl@gmail.com */using System;using System.ComponentModel;using System.Runtime.InteropServices;namespace MLib{	public class Timer	{				// return the number of milliseconds between the previous two updates		public static int DeltaTime		{			get			{				return mDeltaTime;			}		}				// return the absolute number of ticks in milliseconds		public static int Ticks		{			get			{				return (int) (1000 * mTicks / mFrequency);			}		}				// No, it's not a first person shooter.		public static int FPS		{			get			{				return mFPS;			}		}				// If you set this to a value less than 1000, it will limit the speed		// of your program so you don't go above the max fps (in reality you		// will get slightly lower than maxFPS). By default it is set to not		// limit speed at all.		public static int MaxFPS		{			get			{				return 1000 / mMinWait;			}			set			{				mMinWait = 1000 / value;			}		}				// This function should be called once per frame. It updates the timer.		public static void Update()		{			if(!mIsStarted)				Init();						// last frame's ticks			mOldTicks = mTicks;						// Should we do it the easy way...			if(mUseEnvTicks)			{				mTicks = Environment.TickCount;								while(mMinWait > mTicks - mOldTicks)				{					System.Threading.Thread.Sleep(0);					mTicks = Environment.TickCount;				}								mDeltaTime = (int) (mTicks - mOldTicks);			}			// or the QPF way?			else			{				QueryPerformanceCounter(out mTicks);								while(mMinWait * mFrequency / 1000 > mTicks - mOldTicks)				{					System.Threading.Thread.Sleep(0);					QueryPerformanceCounter(out mTicks);				}								mDeltaTime = ((int) (1000 * (mTicks - mOldTicks) / mFrequency));			}						// counts frames and updates every half second			if(mSinceFPSUpdate >= 500){				mSinceFPSUpdate = 0;				mFPS = mTempFPS;				mTempFPS = 0;			}						mTempFPS += 2;			mSinceFPSUpdate += mDeltaTime;		}				// This initializes the timer; it gets called automatically at the first update		static void Init()		{			try			{				// use QPF/QPC if we can				if (QueryPerformanceFrequency(out mFrequency) == false ||				    QueryPerformanceCounter(out mTicks) == false)				{					throw new Win32Exception();				}			}			catch			{				// Function failed; we're not in windows or something's borked				UseEnvClock();			}						// Get everything else set up			mOldTicks = mTicks;			mIsStarted = true;		}				static void UseEnvClock()		{			mUseEnvTicks = true;			mFrequency = 1000;			mTicks = Environment.TickCount;			// Doesn't the framework make everything simpler?			// It's too bad Microsoft didn't implement their			// own framework with a high resolution timer.		}				[DllImport("kernel32.dll")]		static extern bool QueryPerformanceFrequency(out long Frequency);				[DllImport("kernel32.dll")]		static extern bool QueryPerformanceCounter(out long TimeCount);				// private variables		static long mFrequency = 0;		static long mTicks = 0;		static long mOldTicks = 0;		static int mDeltaTime = 0;		static int mMinWait = 0;		static int mFPS = 0;		static int mTempFPS = 0;		static int mSinceFPSUpdate = 0;		static bool mUseEnvTicks = false;		static bool mIsStarted = false;	}}

It's under the zlib license, so you're free to do almost anything you want with it. (The license doesn't stop you from using it in a closed-source, commercial project, for example.)

It has a built-in FPS counter that updates every half second and an optional framerate-limiter that, if used, will use Sleep(0) in the timer update until the minimum wait period has elapsed, but that's turned off by default.

Here's an example use:
using Timer = MLib.Timer;Timer.Init(); //set up the timerTimer.MaxFPS = 100; // if you didn't have this, it wouldn't limit framerate at all, but now it won't go faster than 100 fps//every frame:Timer.Update();int dt = Timer.DeltaTime; //get the length of the last frame (in ms)int absTime = Timer.Ticks; // get the absolute time relative to the system startup (in ms)Console.WriteLine(Timer.FPS.ToString());



Oh, and to whoever said TickCount is only garunteed to 500ms: that's according to Microsoft's documentation and is applicable to Microsoft's implementation of the framework. Mono uses a more accurate timing function, so it returns more accurate results. I didn't have problems using TickCount under Linux, but I definitely did under Windows.


[Edited by - nagromo on February 19, 2006 2:20:49 PM]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.