PDA

View Full Version : Gettickcount and the rest...



seiferalmasy
14-06-2006, 02:10 PM
Looking into game loops rather than using timers. So, my question is:

What is the best and most successful way of using a loop?

I see there are loads of ways to get the time elapsed but which is best and what are the following:

Gettickcount
Timegettime
QueryPerfomanceCounter
QueryPerformancefrequency

So...which is best for a game running at a maximum of say 100 Fps. I am using delphix, I need the game to run as smooth as possible on most machines possible. Obviously I have been on google and found loads of ways to achieve game loops, but with so many options, there must be a "best" way :)

Thanks

tux
14-06-2006, 02:24 PM
TimeGetTime is probably the most accurate and reliable (queryperformance counter isnt reliable)

Traveler
14-06-2006, 07:57 PM
I've been using GetTickCount in Village Defense and sofar there haven't been any problems. That said, I really can't tell if the other are any better or worse.

Robert Kosek
14-06-2006, 08:50 PM
I used the performance queries, but had no problems at all. No irregularities or inconsistencies.

tanffn
15-06-2006, 12:13 AM
TimeGetTime is probably the most accurate and reliable (queryperformance counter isnt reliable)

What do you mean by "isnt reliable", personally I always used GetTickCount only lately I discovered QueryPerfomance. QueryPerfomance should give a much higher resolution timer..

tux
15-06-2006, 07:04 AM
well i read this (http://www.mindcontrol.org/~hplus/pc-timers.html) and im at a loss now

fragle
15-06-2006, 12:35 PM
Umm... not much i could add, 'cause i was fine with GetTickCount's precision till now, but here's another opinion (http://www.virtualdub.org/blog/pivot/entry.php?id=106) about timers. He ended up using timeGetTime btw :)

seiferalmasy
15-06-2006, 12:40 PM
I see even you experts are as confused as me perhaps :P

I have now tested gettickcount and timegettime.

Confirmed that timegettime is bar far the better.

As some of you will have read, my computer seems to be ..well ****ed up somehow, timers are bonkers. Gettickcount, slowed down unbelievably with delphix..and was awful.
Timegettime runs the game perfectly, with no slow down. It works like a dream.

This is my loop procedure, it is probably awfully inefficient, so if there are any pointers you guys can add like "hey that isn't how it is supposed to be done" then tell me:)


Procedure TheGameLoop;
begin

While (Finished=false)do
begin

Application.processmessages;

CurrentTime:=timegettime-StartTime;

If (CurrentTime>TheInterval)then
begin

StartTime:=timegettime;

if not mainfm.DXDraw1.CanDraw then Exit;

-----the rest

Application.processmessages;

end;

end;

end;

Interval is set when you use or don't use Vsync. If Vsync, Refreshrate=monitor refresh, Interval :=1000/Refreshrate. , if no vsync, I set to 1000/60.

Traveler
15-06-2006, 08:07 PM
Looks good to me.

You could argue whether it is necessary to use application.processmessages twice in the loop, but I doubt it'll hurt.

seiferalmasy
15-06-2006, 09:39 PM
I spoke too soon:) It seems there is some problem with some aspect of delphix, directx, my graphic card or Win XP.

Timegettime is indeed running at 1000 ticks a sec. No matter what (as opposed to the timers that seem to run 64 fps regardless)

However, when delphi is not open (or DXDIAG), the speed of the game slows down considerably just like the timers. Timegettime is certainly still running at 1000 a sec, but thr game is for some reason slowing down. Something is stopping it drawing to the screen perhaps.

What ever it is, I expect that this will again be incorporated into the exe. I will test it tomorrow, and I will also be buying a new card soon.

If none of these work, I may have to go back to Win2000, because something is really wrong here. Why the hell would it work proper if delphi is open as opposed to closed?

:?:

aidave
15-06-2006, 09:59 PM
Whats the difference between GetTickCount and TimeGetTime?

Ive never heard of TGT before

seiferalmasy
15-06-2006, 10:01 PM
From what I can gather timegettime is more reliable. Sadly my machine has something wrong...or something weird happening. Timegettime still gets time from number of millisecs since windows started.

You need to have MMsystem in your uses clause to use it. Wish I knew what is making my apps run so weird

Robert Kosek
15-06-2006, 10:07 PM
Try the QueryPerformanceCounter. In fact, here's an example. It's VERY reliable and accurate down to the trillionths of a second.


(************************************************* *****************************
* The contents of this file are subject to the Mozilla Public License *
* Version 1.1 (the "License"); you may not use this file except in *
* compliance with the License. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
* *
* Software distributed under the License is distributed on an "AS IS" *
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the *
* License for the specific language governing rights and limitations *
* under the License. *
* *
* The Original Code is uQTimers.pas. *
* *
* The Initial Developer of the Original Code is Robert "The Wicked Flea" *
* Kosek. Portions created by Robert Kosek are Copyright (C) May 2006. *
* All Rights Reserved. *
************************************************** ****************************)
unit uQTimers;

interface

uses Windows;

type
TQuickTimer = record
Start, Stop: Int64;
end;

{ Returns a number for the running timer. }
function StartTimer: Integer;
{ When given a number the timer will be removed and the result will be
the seconds elapsed. }
function StopTimer(which: Integer): Extended;

var ProcessorSpeed: Int64;
QTimers: array of TQuickTimer;

implementation

function StartTimer: Integer;
begin
Result := length(QTimers);
SetLength(QTimers,length(QTimers)+1);
QueryPerformanceCounter(QTimers[Result].Start);
end;

function StopTimer(which: Integer): Extended;
procedure RemoveIndex(Index: Integer);
var i: integer;
begin
for i := Index to Length(QTimers)-1 do
QTimers[i] := QTimers[i+1];

SetLength(QTimers,Length(QTimers)-1);
end;
begin
QueryPerformanceCounter(QTimers[which].Stop);
Result := (QTimers[which].Stop - QTimers[which].Start) / ProcessorSpeed;

if which = length(QTimers) then
SetLength(QTimers,length(QTimers)-1)
else
RemoveIndex(which);
end;

procedure KillTimers;
begin
SetLength(QTimers,0);
end;

initialization
QueryPerformanceFrequency(ProcessorSpeed);
finalization
KillTimers;
end.

seiferalmasy
15-06-2006, 10:11 PM
That is some good code and will no doubt me very accurate. Sadly, something fundamentally wrong with directx, XP, my gcard (geforce 2), or delphix because even if I try that, the game will not run right if delphi is closed. The exe made will have whatever bug is operating incorporated into it.

I have concluded that it is not just the timers affected, it is alot of other directx things with delphix. I will change gcard soon, and see if there is any difference.

Why would the game work right when delphi is open and nopt work otherwise? What is wrong here? Very frustrating, and it seems only me with this bug?

I have changed the following but still the problem exists:

Processor, motherboard, directx version, memory, delphi version, Delphix version

I did not have these problems however when using win2000 a year ago. The only problem is I do not know if this problem is being compil;ed into the exe regardless. That is why I set out a test in the Delphix section of these forums to test.

maybe SP2 will save me? You think I should try it?

jdarling
16-06-2006, 12:51 PM
maybe SP2 will save me? You think I should try it?

If your talking about XP SP2, then YES. It fixed ALOT of bugs in XP overall, and some of the performance counters were directly (and indirectly) affected.

seiferalmasy
16-06-2006, 02:31 PM
well sp2 was no go, it was bloody awful. half my programs no longer worked, slow down of OS and a host of other unwanted side effects. I have gone back to sp1a. SP2 did not correct the problem I have

I am seriously thinking it might be soley due to my geforce 2 and or the drivers. Tried latest drivers but they cause BSOD with the geforce 2, so they are no go:)

tux
16-06-2006, 03:11 PM
SP1a isnt going to be supported with security patches after october so mabey a format is in order to get SP2 going

seiferalmasy
16-06-2006, 07:02 PM
I did format in the end;)

all went pearshaped anyway, so uninstalled bad evil sp2 , went back to 1a:)

Also, when my graphics drivers are not installed (or running in safe mode), the games and timers run correctly, so most likely is driver/nvidia related :(

not bothered about them security updates from microsoft, good ol Zone alarm does the trick usually:) (but now thats gone, and kaspersky kicks ass) The next OS I get will be in atleast another 2 years, prob a later form of Vista

seiferalmasy
18-07-2006, 02:05 AM
isnt a OS issue (maybe I try win 98 too, just to eb sure). I have changed Graphic card to ATI X800, that wasn't it either. Isn't even a delphix issue.

Its a delphi issue. I have no idea what it is and have all but give in. Will get hold of borland delphi 2005 and see if that too has this strange "bug".

Delphi 7 no change from 4, the same. On idle loops exact the same as the timers. Somehow iterations are being limited. Its all messed up and crazy. I am stuck well and truly.

Will keep you posted

Edit:

Borland 2005, same problem. And my my what an AWFUL GUI 2005 has...I will never ever use that!

seiferalmasy
18-07-2006, 09:24 PM
Cerianly not the processor. Even with affinity set to use 1 processor, still there (and HT is off on my machine). When I take the program to another persons house, 4 so far, the problem still exists. When I was using an athlon 1800+ with a completely different computer, the problem was the same.
It is very strange. I will now upload a new test file, since the other is dead. This one doesn't use timers. It uses a loop and unfortunately for me, the problem is STILL there.

This is not a timer problem at all....its worse. Seems that iterations are being limited?


It is possible maybe that maybe some hardware I have has affected the proper running of my computer. Even when all drivers were not installed however, the problem still existed.

The things I have never changed recently are:

Soundcard, Hauppauge 350, Pinnacle Studio 9 DV. Other than that my entire system has changed once including delphi version. I have used delphi 4, 7 and 2005, all same result I have used Win 2000 with SP4 and XP. Same result. Infact, the code below will show you that indeed there is something truly bizarre going on:



While (Finished=False)do
begin
Currenttime:=Timegettime-StartTime

If (CurrentTime>9)then
begin
inc(TheCounter);
Starttime:=TimegetTime;
end;

Application.processmessages;

end;


Explanation: When currenttime is greater than 9, the counter is incremented. This incremementation should be 100 times a second, since timegettime is the same as gettickcount.

What Happens: If dxdiag or msn messenger (to name 2) applications are not open on screen the result is wrong. What happens on my machines atleast (and then all machines I take the exe to) is the counter goes up 32 times a second..and not 100. All values in the place of 17 display erroneous results. If I set to 100, i would expect TheCounter to go up by 10 times a second but that is not what I get.

The Test:

My test is simple. I have a link below which basically has the code above. I want you people to test the following:

1. If the exe supplied with the program counts up 100 times a second.
2. Once YOU have compiled it, does it go up by 100 times a second.
3. Send me YOUR compiled exe so I can test it on my machine and other peoples machines

This will tell me:

1. If the problem is machine dependant
2. If the problem is soley because I am compiling the source

Thanks to all that participate but if this problem still exists on my test machines (4 of them) with YOUR exe's, then we have a major problem here.....

Please note that the program has a normal timer on the form. This counts TEN seconds and then gives you the number of seconds elaspsed when The Counter has reached 1000. In other words, the above example should give 1000 (100 a sec * 10 sec) after 10 seconds Smile If it does not...the result follows the pattern I am experiencing.

If it does not also try having dxdiag opened, or msn messenger open on screen (not in task bar).

---------
Bottom line:

Why the hell would dxdiag or msn messenger being opened sort the problem out temporrarily as long as they are open on my machine? (this "fix" doesn't seem to work on any machine I test the exe on so far apart from my own). The problem still exists when application.processmessages is not called, so that isn't it either:)

Seifer Almasy

THE TEST FILE WITH SOURCE.

MY FTP SERVER:

If you want to access through IE (note mozilla firefox cannot upload):

FTP://thetest@DLPB.kicks-ass.net:2121 (leave password box empty)

If you want to access through FTP client (recommended):

Server: DLPB.kicks-ass.net
Port 2121
Username: thetest (case sensitive)
Pass: (no password)


Remember please, to upload your own exe after compiling the source on your end. Upload back to my FTP server if you will (give it a unique name).

Thanks to all that participate, because of this problemis not just me, then all of us using idle loops etc for our delphi games are in trouble on atleast a significant amount of machines. Hopefully it is just me;)

Clootie
18-07-2006, 10:38 PM
Add:
timeBeginPeriod(1); // this could be at start of program
.....
{ here should be your high precision code with timeGetTime calls }
.....
timeEndPeriod(1); // this could be at the end of program

seiferalmasy
18-07-2006, 11:19 PM
BREAKTHROUGH UNITED

THANKYOU CLOOTIE! That seems to have done it:) also sorts out normal timers that I had issues with, like dxtimer and threadedtimer

What was the problem? Why didn't it work before?

Logically it should work without that code?

Do tell:)

btw: 18th July was my birthday, so I guess after around 2 years of this problem, it is my present;)

Clootie
19-07-2006, 05:54 AM
:D

timeBeginPeriod() increases internal precision of Windows timer. From default of 10-20 (? - I cant't recall this without documentation) to values you passed to it. One of the side-effects: Windows start to switch concurrently running threads with precision of timeBeginPeriod too (technicaly this means that tBP sets threads time-slice).

seiferalmasy
19-07-2006, 05:57 AM
would you go about doing loops any differently? Or would you, like me, be forced to use timebeginperiod?

Clootie
19-07-2006, 10:01 AM
I prefer to use QueryPerfomanceCounter / QueryPerformancefrequency.
And by the way: DX9 invokes timeBeginPeriod(1) automaticaly (at the initialization)...

seiferalmasy
19-07-2006, 10:03 AM
hmmm I hear that on certain machines, there is a bug which can propell the timer a certain no of secs into the future (no i meant a no of seconds are added on...skipped, its just I have been revising paradox theory and back to the future haha) using that method.

In any case when you said DX9, you are referring to my DXDIAG question? And by that, I also take it msn must also, somewhere invoke Timenbeginperiod(1)?

Clootie
19-07-2006, 10:25 AM
"Jumping" behaviour of QPC AFAIK is happens only on really old motherboards of Pentium1 era. But in this thread fraggle already posted link stating other (more modern) bug in QPC implementations on different platforms: http://www.virtualdub.org/blog/pivot/entry.php?id=106


By DX9 I mean any application that initializes Direct3D9. And calling timeBeginPeriod has system level influence.

seiferalmasy
19-07-2006, 10:27 AM
well little more to say except, thanks for making my birthday and I think your signiture is pretty witty:) 2 kinds of people....1 and 0...yes very witty:)

Sly
19-07-2006, 01:27 PM
The fix for multiple CPUs is easy. Call SetThreadAffinity() to force your process thread to run on one CPU. Then you will not get the effect of the TSC or QPC jumping back and forth due to the thread being switched from one CPU to the other.

Here is the code for a better timer class derived from a C++ class that I found a few years back. It uses both the QPC and timeGetTime to solve the problem of inaccurate QPC clocks.

// Original C++ source by Oleg Pudeyev
// Converted from original C++ source by Steve 'Sly' Williams
// Converted to use ticks internally instead of seconds for greater accuracy
//
// Original comments:
// I took timer and frame rate code from DXUtil.h/cpp (common DX sample code).
// You can find it in DXSDK\Samples\Multimedia\Common\Include and Src.
// Instead of using a single function with static data, I organized the code into
// classes, which allows for additional flexibility.
// Initialization (mmtime/performance frequency selection) is now performed on startup,
// in a dedicated class. I also added timeBeginPeriod/timeEndPeriod calls.
// CFrameRateCounterEx is my own creation.
//

// This code fixes the "feature" described in Q274323,
// "Performance Counter Value May Unexpectedly Leap Forward":
// http://support.microsoft.com/default.aspx?scid=kb;en-us;Q274323

unit BetterTimer;

interface

type
TBetterTimer = class
private
// Time elapsed before last pause, or 0 if the timer was never paused
m_ElapsedTime: Int64;
// Time in 'timer time' (seconds) when this timer was last resumed
m_ResumedTimeStamp: Int64;
// Time in QueryPerformanceTimer-counts converted to seconds
// when this timer was last resumed
m_LastTimeStampQPC: Int64;
// The same time in milliseconds as obtained from timeGetTime
m_LastTimeStampMMT: Cardinal;
// A counter indicating whether the counter is active.
// If it is greater than zero, the counter is running, otherwise it is paused
m_Running: Integer;
public
constructor Create;
function GetTime: Single;
procedure Pause;
procedure Reset;
procedure Resume;
end;

implementation

uses
Windows, MMSystem, SysUtils;

// The maximum deviation from timeGetTime reading that we will tolerate, in milliseconds
const
PerfCounterTolerance = 1000;

var
// This variable is TRUE if performance counter is available
gs_HavePerformanceCounter: Boolean = False;
// If performance counter is available, this variable contains 1/its resolution -- number of seconds in each count.
// It's a floating point value mostly for convenience.
gs_CountsPerSecond: Int64;

{ TBetterTimer }

constructor TBetterTimer.Create;
begin
inherited;
// Start and reset the timer
m_Running := 1;
Reset();
end;

function TBetterTimer.GetTime: Single;
var
QPCTime, DeltaQPC, TimeDelta: Int64;
MMTTime, DeltaMMT, DeltaQPCinMS: DWORD;
begin
Result := m_ElapsedTime / gs_CountsPerSecond;

// If the timer is paused, no time has passed since the pause time
// and all passed time is stored in elapsed time variable, so return that
if m_Running <= 0 then
Exit;

// Otherwise, retrieve current time, subtract last resumed time from it, add
// elapsed time, and return the result adjusted for possible QPC leaps
if gs_HavePerformanceCounter then
begin
// The code is the same as in Pause function
// Get current time in ticks
QueryPerformanceCounter&#40;QPCTime&#41;;
// Determine the time difference between this and previous QPC query
DeltaQPC &#58;= QPCTime - m_LastTimeStampQPC;
// Get current mmtimer time
MMTTime &#58;= timeGetTime&#40;&#41;;
// And the difference between currnent and previous mmtimer query
DeltaMMT &#58;= MMTTime - m_LastTimeStampMMT;
// Check if the performance counter leaped forward,
// which is when difference in values returned by QPC and mmtimer is more than
// the predefined PerfCounterTolerance value
// Since all times are returned as unsigned variables, care must be taken when subtracting
// because we don't want leap adjustment to be applied in case mmtimer is lagging behind QPC
DeltaQPCinMS &#58;= DeltaQPC * 1000 div gs_CountsPerSecond;
if &#40;DeltaQPCinMS > DeltaMMT&#41; and &#40;DeltaQPCinMS - DeltaMMT > PerfCounterTolerance&#41; then
begin
// Performance counter leaped forward
// Adjust the elapsed time by the difference between QPC and mmtimer delta times
m_ElapsedTime &#58;= m_ElapsedTime - DeltaQPC - DeltaMMT;
end;
// Calculate total delta time since timer was reset
TimeDelta &#58;= QPCTime - m_ResumedTimeStamp + m_ElapsedTime;
// Update current timer timestamps
m_LastTimeStampQPC &#58;= QPCTime;
m_LastTimeStampMMT &#58;= MMTTime;
// Return calculated delta
Result &#58;= TimeDelta / gs_CountsPerSecond;
end
else
begin
// If we're using mmtimer, just return the time passed since last resume
// plus the elapsed time that passed before last resume
// No adjustments are necessary
Result &#58;= &#40;timeGetTime&#40;&#41; - m_ElapsedTime + m_ResumedTimeStamp&#41; / gs_CountsPerSecond;
end;
end;

procedure TBetterTimer.Pause;
var
QPCTime, DeltaQPC&#58; Int64;
MMTTime, DeltaMMT, DeltaQPCinMS&#58; DWORD;
begin
Dec&#40;m_Running&#41;;
// Allow for nested pause/resume calls.
// Only pause if active count reaches zero
if m_Running <> 0 then
Exit;

// Update the elapsed time
if gs_HavePerformanceCounter then
begin
// Get current time in ticks
QueryPerformanceCounter&#40;QPCTime&#41;;
// Determine the time difference between this and previous QPC query
DeltaQPC &#58;= QPCTime - m_LastTimeStampQPC;
// Get current mmtimer time
MMTTime &#58;= timeGetTime&#40;&#41;;
// And the difference between currnent and previous mmtimer query
DeltaMMT &#58;= MMTTime - m_LastTimeStampMMT;
// Check if the performance counter leaped forward,
// which is when difference in values returned by QPC and mmtimer is more than
// the predefined PerfCounterTolerance value
// Since all times are returned as unsigned variables, care must be taken when subtracting
// because we don't want leap adjustment to be applied in case mmtimer is lagging behind QPC
DeltaQPCinMS &#58;= DeltaQPC * 1000 div gs_CountsPerSecond;
if &#40;DeltaQPCinMS > DeltaMMT&#41; and &#40;DeltaQPCinMS - DeltaMMT > PerfCounterTolerance&#41; then
begin
// Performance counter leaped forward
// Adjust the elapsed time by the difference between QPC and mmtimer delta times
m_ElapsedTime &#58;= m_ElapsedTime - DeltaQPC - DeltaMMT;
end;
// Add the time passed since last resume to the elapsed time variable
m_ElapsedTime &#58;= m_ElapsedTime + QPCTime - m_ResumedTimeStamp;
// Don't update last polled time stamps for QPC and mmtimer
// since they will be updated in Resume method
end
else
begin
// If we are using mmtimer, just add the time passed since last resume time
// to the elapsed time variable
m_ElapsedTime &#58;= m_ElapsedTime + timeGetTime&#40;&#41; + m_ResumedTimeStamp;
end;
// The timer is now paused
end;

procedure TBetterTimer.Reset;
begin
// This function initializes or resets the timer
if gs_HavePerformanceCounter then
begin
// Retrieve the last resumed time stamp
QueryPerformanceCounter&#40;m_ResumedTimeStamp&#41;;
m_LastTimeStampQPC &#58;= m_ResumedTimeStamp;
// To correct for unexpected leaps, retrieve the same time from the multimedia timer
m_LastTimeStampMMT &#58;= timeGetTime&#40;&#41;;
end
else
begin
// There are no issues with multimedia timer, so just get the current value
// and write it to the last resumed stamp
m_ResumedTimeStamp &#58;= timeGetTime&#40;&#41;;
end;

// Timer hasn't been paused, so set elapsed time to zero
m_ElapsedTime &#58;= 0;
end;

procedure TBetterTimer.Resume;
begin
Inc&#40;m_Running&#41;;
// Allow for nested pause/resume calls.
// Only resume if active count reaches one
if m_Running <> 1 then
Exit;

// Update the last resumed time stamp
if gs_HavePerformanceCounter then
begin
// Get current time in ticks
QueryPerformanceCounter&#40;m_ResumedTimeStamp&#41;;
m_LastTimeStampQPC &#58;= m_ResumedTimeStamp;
// Get the current time from mmtimer as well for QPC adjustments
m_LastTimeStampMMT &#58;= timeGetTime&#40;&#41;;
end
else
begin
// For mmtimer, just retrieve the current time
m_ResumedTimeStamp &#58;= timeGetTime&#40;&#41;;
end;
end;

var
// Set the highest resolution for the multimedia timer
tc&#58; TIMECAPS;

initialization
// QueryPerformanceFrequency returns a BOOL value indicating if a performance counter is available
gs_HavePerformanceCounter &#58;= QueryPerformanceFrequency&#40;gs_CountsPerSecond&#41;;
if not gs_HavePerformanceCounter then
gs_CountsPerSecond &#58;= 1000;
// Retrieve timer caps, which contain resolution range
timeGetDevCaps&#40;@tc, SizeOf&#40;tc&#41;&#41;;
// Set resolution with this call
timeBeginPeriod&#40;tc.wPeriodMin&#41;;

finalization
// Don't retrieve the caps again to be absolutely sure we restore the same value that we set
// Restore old resolution
timeEndPeriod&#40;tc.wPeriodMin&#41;;

end.

jdarling
19-07-2006, 01:32 PM
This same problem can occur on a HyperThreading processor as well. Try placing SetThreadAffinityMask(GetCurrentThread(), 1); in your application initialization code (NOT FORM CODE, APP CODE).

Sorry, some how I overlooked this thread till now :). As the post above said, that typically fixes any issues, personally I've modified my timer units to have this in their initialization.

seiferalmasy
24-07-2006, 11:03 AM
Last question on this topic for me thankgod:)

Around the place I see people using Sleep(1) in their loops to cut down on cpu.

Is this appropriate for game use and also do any of you use soemthing superior?

jdarling
24-07-2006, 01:36 PM
Last question on this topic for me thankgod:)

Around the place I see people using Sleep(1) in their loops to cut down on cpu.

Is this appropriate for game use and also do any of you use soemthing superior?

No, Sleep(1) is not proper if you are writing a multithreaded application. If your in single thread mode, then it still is not proper, but is used quite frequently.

A good article to learn from: http://eonclash.com/Tutorials/Multithreading/MartinHarvey1.1/ToC.html
I didn't write it, but since Martin Harvey's website is gone now, I've posted it so that others can get to it. Hopefully we see a new site jump up for Martins work.

seiferalmasy
24-07-2006, 07:30 PM
hmm well being an almost complte novice, would you say that my normal delphix app is multithreaded?
and I have heard it is better to use threads in games...how does one go about this? Any tutorial?

jdarling
24-07-2006, 08:07 PM
In games, yes, typically you are multithreaded. DelphiX hides some of the threading for you (depending upon the version your using). Reading the tutorial that I posted above will help out. Its written from start to finish for people new to threading and has stuff in it for those used to doing threaded development.

Clootie
25-07-2006, 09:21 AM
No, Sleep(1) is not proper if you are writing a multithreaded application. If your in single thread mode, then it still is not proper, but is used quite frequently.
Can you explain why you think that Sleep(1) is not proper way to cut down on cpu?

jdarling
25-07-2006, 01:54 PM
Can you explain why you think that Sleep(1) is not proper way to cut down on cpu?

I don't have a problem with Sleep in general, its peoples lack of understanding of what Sleep actually does, and how to implement it. I'd argue against ever using Sleep(1+).

I'll admit, that calling sleep inside of a thread is quite common (outside of Real Time stuff). I'll also admit to calling Sleep(0) inside of my game loops to lower CPU head (notice I didn't say Sleep(1)). My problem with sleep isn't in these situations, its in the situation where the developer doesn't understand what is going to come from them doing it. Sleep(0) achieves your lowered CPU by simply telling the system to go ahead and process others, then return to me. Sleep(1) tells the system to wait AT LEAST 1ms.

First off is that sleep can't be canceld, once called you are forced into the wait. This means that on a slower system where a different down time may be necessary it won't be achieved properly. Utilizing it inside of the main loop of some applications may work just fine it still breaks this area. Sleep is not definitive, calling Sleep(1) inside the loop may allow another heavy weight application (or thread) to take over and hold you out of the loop. Thus a Sleep(1) may come close to a Sleep(2+), not a big deal until you add that up a few thousand times. Sleep(0) doesn't require the test for greater, nor the call to the processor timer, thus has a lower call time.

The largest problem with using Sleep is that people don't realize that you had it inside of a large loop. Thus they turn around and use it inside of a tight loop and wonder why they get inconsistent results on different systems. Code such as the following pops up:

Proper - Origional code
begin
while(true) do
begin
// Calculate frame difference
// Game Code
sleep(0);
end;
end;


Acceptable - Minor "Tweak" cuz they thought it was right
begin
while(true) do
begin
// Calculate frame difference
// Game Code
sleep(1);
end;
end;


Bad Idea - Hmmm... Need more time between frames, this should work
begin
while(true) do
begin
StartFrame := GetTickCount;
// Game Code
while GetTickCount-StartFrame < 100 do // Supposidly 10 FPS
sleep(1);
end;
end;

Yes, vetrans know that this is a horrid idea, we know why, and we know how to fix it. But the novice that looked at our code didn't know it. They saw something similar and thus decided that the code above was a good idea.

In fact, I've seen tutorials and demos written with "Delay" routines that are bound to ProcessMessages and Sleep. I can write code that achieves the same thing with about 1/2 of the overhead.

Even Worse (but unfortunately common, but quoted as being a proper delay sequence) - Hey I need a proper dealy routine, this looks right
procedure Delay(dwMilliseconds: Longint);
var
iStart, iStop: DWORD;
begin
iStart := GetTickCount;
repeat
iStop := GetTickCount;
Application.ProcessMessages;
Sleep(1);
until (iStop - iStart) >= dwMilliseconds;
end;

Now, moving into the threading department. Utilizing Sleep(1) to "Lower CPU" in a multi-threaded application is nothing more then adding even more overhead on single processor systems, and can remove the reasoning for multiple processors to even be present. For anything more the Sleep(0), try utilizing OS Timer Objects. They exist for Win2k+ and Linux. They are more reliable, and achieve the lowered processor utilization, and they don't impart a cost comparativly speaking.

In .NET Thread.Sleep is another story (from what my .NET developers tell me). Since its managed, I would immagine that they are correct.

Implementing a threaded spin lock in Delphi requires you to perform a Sleep(0), at least in Delphi 7 and below, I can't speak to over Delphi 7. Simply put, this is due to Delphi not passing the processor lock's properly in assembly. Read up you can find it.

seiferalmasy
25-07-2006, 02:02 PM
well i am no expert, but during my tests using sleep 1 actually increased the game performance especially on bog-slow computers :( from something impossibly slow to something reasonable. Even on my machine there is noticble smoothness increase, cpustays at 80-90 with sleep (1). I will keep reading these comments:)

Fran
25-07-2006, 06:40 PM
I've been writing multithreaded apps for quite a bit, and i'm of the opinion that sleep does a fine job of passing control away from a thread, thus allowing others to get to it.

There is however one major downside to using it. It's very inacurate. Sleep(1) will return 1-15ms later, rarely 1. With nothing else happening on the system, the average on my fast computer is 2-3ms, but as soon as something else is going on, it's 10-15ms.

Here's some code to try it out, if you feel like it, start another thread that does something and watch the numbers soar:



program Project1;

uses
windows,sysutils;

procedure go_test;
var
start_tick,end_tick,now_tick,switches,avg_ticks&#58;lo ngint;
begin
switches&#58;=0;
start_tick&#58;=gettickcount;
end_tick&#58;=start_tick+5000;

repeat
sleep&#40;1&#41;;
inc&#40;switches&#41;;
now_tick&#58;=gettickcount;
until now_tick>=end_tick;

avg_ticks&#58;=&#40;now_tick-start_tick&#41; div switches;

messagebox&#40;0,pchar&#40;'Each 1ms switch took on average '+inttostr&#40;avg_ticks&#41;+'ms'&#41;,'Result',mb_ok&#41;;
end;

begin
go_test;
end.

Fran
25-07-2006, 06:56 PM
Try the QueryPerformanceCounter. In fact, here's an example. It's VERY reliable and accurate down to the trillionths of a second.


Hum... it's been a while, but i'm pretty sure this high precision timer is equivalent to:


function timer_processor_ticks&#58;int64;assembler;
asm
rdtsc
end;

That's the internal timestamp of the processor (counter incremented by one every processor tick). That is going to be incredibly accurate on processors that never change speeds, but will be awfully inacurate on processors that do (notebooks, many newer desktop ones that do power conservation).

If you are running a timed loop, timegettime and gettickcount will likely be your best bet, but you need to account for the possibility that the counters will decrement or jump way ahead once in a blue moon. Sometimes windows realizes it's made a mistake a makes a drastic change to the counters to fix things up.

It happens very rarely on modern processors, but can still happen with some power events and time adjustments (like winter/summer time thing).

seiferalmasy
25-07-2006, 07:20 PM
It's very inacurate.

Is it still inaccurate even after calling Timebeginperiod(1) (which i can't thank clootie enough for) at the beginning of your app...i have found it returns exactly 1 - 2 ms afterwards if this is implemented.

Fran
25-07-2006, 07:43 PM
It's very inacurate.

Is it still inaccurate even after calling Timebeginperiod(1) (which i can't thank clootie enough for) at the beginning of your app...i have found it returns exactly 1 - 2 ms afterwards if this is implemented.

So sleep(x) is affected by timebeginperiod? Well, i never thought it would be, so i never tried, maybe i will next time :o

seiferalmasy
25-07-2006, 07:44 PM
Yes I believe so...it relies on internal clock, and a dude from another site has done numerous tests and quotes "Not a system tried on yet where the accuracy goes below 1ms, 2 at the most.

It also affects normal timers too, like dxtimer and even threadedtimer. I had a MAJOR problem until clootie sorted it

Clootie
25-07-2006, 09:04 PM
There is however one major downside to using it. It's very inacurate. Sleep(1) will return 1-15ms later, rarely 1. With nothing else happening on the system, the average on my fast computer is 2-3ms, but as soon as something else is going on, it's 10-15ms.
As it's already been said: call timeBeginPeriod(1); and measure your accuracy again. You should be surprized!

seiferalmasy
25-07-2006, 09:24 PM
someone should really publicise that fact alot more. Its not common knowledge and it really is very very important for the maintenence of timers, sleep and even game loops.

Fran
25-07-2006, 10:50 PM
There is however one major downside to using it. It's very inacurate. Sleep(1) will return 1-15ms later, rarely 1. With nothing else happening on the system, the average on my fast computer is 2-3ms, but as soon as something else is going on, it's 10-15ms.
As it's already been said: call timeBeginPeriod(1); and measure your accuracy again. You should be surprized!

I certainly will, at one time i needed very high accuracy, and used some rather esoteric methods to achieve it. The only reason i never tried timebeginperiod is because the documentation says it's only for the multimedia timers, i never expected it to work on sleep.

In any case, i'll be testing this within the week under some pretty large workloads and heavy thread usage. I'll post here the results with/without those 2 lines of code.

I_am_root
26-07-2006, 05:23 PM
How do you get the best accuracy between 2 function calls(or whatever)? If you give an answer, please give it MultiPlatform(not dependent on Windows API calls). Please give some code. Thanks in advance!


Note : I use Windows+Linux with FreePascal.

Fran
30-08-2006, 10:44 PM
Finally, my answer :)

Ok, so sleep by itself is quite precise, sleep(1) will sleep for 1ms to a bit more (normally more like 1.5, rarely 3 or over) depending on load, but... and what a big but... the measuring unit, gettickcount, is not precise.

Gettickcount has a resolution of 15-16ms, i think it's exact resolution is 1/64 of a second, or 15.625ms, so reading the time with gettickcount will give odd results. For example you'll take a reading of 1000, and the next one will be 1015 or 1016. That doesn't mean that if you sleep(1) you won't get control back at 1005, 1006, or whatever, but if you read gettickcount to check what time it is now, it will give you an imprecise measure (1000 until it's 1015 for example).

So what must be done is using timegettime instead. So far it seems to me the resolution is always 1ms, but the documentation (and earlier posts seem to confirm this) tell us that we must call timebeginperiod(1) prior to any reading and timeendperiod(1) post any reading, to ensure that such a precision will be available.

Allright, what does this mean in code?



begin
timebeginperiod&#40;1&#41;;
ilast_tick_count&#58;=timegettime;

while not iterminating do
begin
tick_count&#58;=timegettime;
if tick_count<>ilast_tick_count then
begin
inc&#40;itime,tick_count-ilast_tick_count&#41;;
ilast_tick_count&#58;=tick_count;

do_stuff!!!;
end;

inc&#40;this_is_a_counter&#41;;
sleep&#40;1&#41;;
end;

timeendperiod&#40;1&#41;;
end;


This will call do_stuff every 2ms on average. If you replace timegettime by gettickcount, do_stuff will be called every 16ms on average.

Note that in both cases this_is_a_counter will be identical however, since sleep works the same anyway.

Last note, the timebeginperiod and timeendperiod can be removed if using gettickcount since they are used to improve the resolution of the reading of the counter read by timegettime, not the internal windows counter... or so i believe. Sleep does not seem to be affected by that command whatsoever.

Hope that helps someone, i pulled many hairs trying to figure out where the missing time was when using gettickcount. Stupid gettickcount, i'll never use you again!!!!!! EVER!!!!!!

ps: i think the resolution of gettickcount depends on the machine and or the os, so it may be different for you than for me.... sigh....

Robert Kosek
30-08-2006, 10:50 PM
I just found this recently, but it might help you. Check this post (http://www.afterwarp.net/forum/post4804-2.html) at Asphyre.net and it might help.

seiferalmasy
01-09-2006, 06:00 PM
"Sleep does not seem to be affected by that command whatsoever"

Apperently it does. On my computer Gettickcount is 1 ms when timebeginperiod(1) is called. And one guy who did full study saus sleep goes to 2ms accuracy at the least with sleep

Fran
01-09-2006, 11:32 PM
"Sleep does not seem to be affected by that command whatsoever"

Apperently it does. On my computer Gettickcount is 1 ms when timebeginperiod(1) is called. And one guy who did full study saus sleep goes to 2ms accuracy at the least with sleep

Well, i stand corrected then. However, i can assure you that gettickcount will not have 1ms accuracy on all windows computers, and will have ~16ms accuracy on at least some, if not many. So if you must have ~2ms accuracy, timegettime + timebegin(end)period(1) should be used, but if you don't mind getting 16ms accuracy on at least some computers, gettickcount can be used.

seiferalmasy
02-09-2006, 04:00 AM
strange that it should work on some but not others....well timegettime works atleast and Queryperformancecounter no doubt works well.



Start&#58;=gettickcount;
sleep&#40;1&#41;;
sleep&#40;1&#41;;
sleep&#40;1&#41;;
sleep&#40;1&#41;;
sleep&#40;1&#41;;
sleep&#40;1&#41;;
sleep&#40;1&#41;;
sleep&#40;1&#41;;
sleep&#40;1&#41;;
sleep&#40;1&#41;;
Theend&#58;=Gettickcount;
Label1.caption&#58;='time elapsed&#58; '+floattostr&#40; &#40;Theend-Start&#41;&#41;+'ms.';

My small test (obviously mmsystem in uses). With timebeginperiod(1) 40 ms elaspses (4ms accuracy with sleep). Without it, the time that elapses is 156ms. So in my case there certainly is a massive shift in accuracy when Timebeginperiod is set to 1.

However, if I set only one sleep(1) you are correct the accuracy stays at 16ms. This doesn't make sense?

EDIT:

Gettickcount seems unreliable, it can measure 10 sleeps at 4ms accuracy but not 1 to any accuracy. So I use timegettime, and low and behold the above example using one sleep (not 10) comes back as 2 ms elapsed. Therefore sleep is indeed affected by timebeginperiod but Gettickcount is unreliable at returning the value of elapsed time,wrong after one sleep, nearer after 10.



Start&#58;=timegettime;
sleep&#40;1&#41;; &#40;will give atleast 2ms accuracy&#41;
Theend&#58;=timegettime;
Label1.caption&#58;='time elapsed&#58; '+inttostr&#40; &#40;Theend-Start&#41;&#41;+'ms.';

The above code will prove that sleep can be accurate to 2ms. Use ten sleep(1) and ity will return 20ms. Please do confirm this for me on your end

NOTE: IT MATTERS WHERE YOU PLACE TIMEBEGINPERIOD(1). IT SHOULD BE IN THE ONCREATE OF YOUR FORM. THE TIMEENDPERIOD(1) IN THE ONCLOSE.

If you put it in the button click, the accuracy comes out as 15-16ms like before.

Lowercase
04-09-2006, 08:58 PM
I always thought that historically a tick was equal to 20 ms (based on the first computer frequency),

It's quite far now for me, maybe it's not the same "tick" :read: