.:: Bots United ::.

.:: Bots United ::. (http://forums.bots-united.com/index.php)
-   General Programming (http://forums.bots-united.com/forumdisplay.php?f=25)
-   -   need your advice on clock() (http://forums.bots-united.com/showthread.php?t=341)

Pierre-Marie Baty 13-01-2004 02:14

need your advice on clock()
 
I'm making a facility to get rid of gpGlobals->time everywhere I can.

This function is supposed to return the time in seconds since the process started, even after the clock() value has overflown.

Since I don't want to wait 24 days to check if my function works, could someone tell me if my implementation is correct ?
Code:

float ProcessTime (void)
{
  // this function returns the time in seconds elapsed since the executable process started.
  // The rollover check ensures the program will continue running after clock() will have
  // overflown its integer value (it does so every 24 days or so). With this rollover check
  // we have a lifetime of more than billion years, w00t!
  // thanks to botmeister for the rollover check idea.
 
  static long current_clock;
  static long prev_clock = 0;
  static long rollover_count = 0;
  float time_in_seconds;
  float rollover_difference;
 
  current_clock = clock (); // get system clock
 
  // has the clock overflown ?
  if (current_clock < prev_clock)
          rollover_count++; // omg, it has, we're running for more than 24 days!
 
  // now convert the time to seconds and calculate the rollover difference
  time_in_seconds = (float) current_clock / CLOCKS_PER_SEC; // convert clock to seconds
  rollover_difference = (float) 286331153 / CLOCKS_PER_SEC * rollover_count;
 
  prev_clock = current_clock; // keep track of current time for future calls of this function
 
  // and return the time in seconds, adding the overflow differences if necessary.
  return (time_in_seconds + rollover_difference);
}


botmeister 13-01-2004 03:35

Re: need your advice on clock()
 
Since your function is simply counting how many seconds have elapsed, try this instead

Code:

static float time_in_seconds = 0;
static long prev_clock = 0;
long current_clock;
current_clock = clock();
if (current_clock < prev_clock)
time_in_seconds += ( (float) current_clock + 1 ) / CLOCKS_PER_SEC;
else
time_in_seconds = (float) current_clock / CLOCKS_PER_SEC;
return time_in_seconds;

You may want to use double instead of float because float does hot have enough precision from some time conversion operations.

btw, why won't my code format the way I want it to? :(

Pierre-Marie Baty 13-01-2004 03:52

Re: need your advice on clock()
 
I am under the impression that your code assumes that the difference between current_clock and prev_clock is normally always 1. Is this correct ?

This is not the case, and can't be, because this function can be called anytime and it can elapse minutes (or hours) between two successive calls, and thus the current_clock and prev_clock variables could differ greatly. And time_in_seconds wouldn't have been updated since last call, and thus would be completely obsolete.

There may be something I don't understand, but to me it seems your code is heavily buggy :)

botmeister 13-01-2004 05:04

Re: need your advice on clock()
 
oops, you are right, the code I posted won't work for at least 2 or 3 different reasons 8o

Let's have a look at this one instead :)

static float time_in_seconds = 0;
static long prev_clock = 0;
long current_clock;
float time_diff;


current_clock = clock();

if (current_clock < prev_clock)
{

// calculate ticks elapsed up to rollover event
time_diff = LONG_MAX - prev_clock;

// add on ticks elapsed after rollover event
time_diff = time_diff + current_clock + 1;
}
else

time_diff = current_clock - prev_clock;

prev_clock = current_clock;
time_in_seconds += time_diff / CLOCKS_PER_SEC;
return time_in_seconds
Note
The function MUST be called at least once between every rollover event!

DAMN! I still have no idea how to copy/paste code into this thing so it shows up the way I want it to. I need a tutorial or something.

Pierre-Marie Baty 13-01-2004 06:29

Re: need your advice on clock()
 
Your code doesn't format right because you're using tabs as separators... and you can't write tabs in the "post reply" text boxes. I'm not sure there's anything you can do but to change your tabs to spaces...

Back on track, basically all I wanted to know was whether my function would work. Does it ? Assuming it does, what's the advantage of yours over mine ?

botmeister 13-01-2004 08:51

Re: need your advice on clock()
 
Working with clock ticks is so much fun and so very confusing :)

I like what you've come up with, and you've got me revisting some of my existing "clock" code which probably can be made simpler and faster without losing any precision.

Anyhow, I looked over your code (and over) and found some minor things which are very subtle and may not matter much depending on the application, but there's one part which appears to be incorrect.

Here goes ...

static long current_clock;

The above line does not have to be static, however you may gain a performance edge by not allocating stack space for it on each call.

This line has a small error in it:

time_in_seconds = (float) current_clock / CLOCKS_PER_SEC;

After a rollover event, the elapsed time in seconds is actually:

time_in_seconds = (float) ( current_clock + 1 ) / CLOCKS_PER_SEC;

The extra clock tick is added because the rollover looks like this (assuming a MAX value of 2)

Code:

Clock ticks --->
0 1 2 0 1 2 0 1 2
        A B

The elapsed time from tick A to tick B is not 0 ticks, but 1 tick, however the exsisting calculation assumes 0 ticks elapsed, which is technically incorrect, but insignificant for timing the actions of a bot.

This line I cannot understand:

rollover_difference = (float) 286331153 / CLOCKS_PER_SEC * rollover_count;

What does 286331153 represent? If we are calculating the number of seconds elapsed during each rollover, then you should be using 2147483647 (2^31 - 1) as the constant (if I typed it in right). You should use the macro LONG_MAX to make the code machine independant.

(float) 286331153 / CLOCKS_PER_SEC is an inline division of two constants, you may as well precalculate this. The compiler may do this for you, but it would be cleaner to precalculate elsewhere.

You may want to replace the data type "float" with "double" because float does not have enough precision to get exact timing conversions in all cases.

The alternative function I posted reduces the number of divisions and multiplications which are computationally more expensive than addition and subtraction, it should reduce precision errors, and it should correct for all the errors I found (I hope anyway).

What I'd do next is replace float with double, and turn the function into a fully contained class object. There's probably still some performance improvements to be made in there as well.

Pierre-Marie Baty 13-01-2004 17:27

Re: need your advice on clock()
 
Quote:

Originally Posted by botmeister
This line has a small error in it:

time_in_seconds = (float) current_clock / CLOCKS_PER_SEC;

After a rollover event, the elapsed time in seconds is actually:

time_in_seconds = (float) ( current_clock + 1 ) / CLOCKS_PER_SEC;

...and one bug, one! :)
Thanks.
But instead of adding +1 to current_clock ONLY IF a rollover has happened, I'd rather add 1 to LONG_MAX (sounds more logical) - no worry, LONG_MAX won't overflow, since I'll cast it to (double) beforehand.

rollover_difference = ((double) LONG_MAX + 1) / CLOCKS_PER_SEC * rollover_count;

Quote:

This line I cannot understand:
Quote:


rollover_difference = (float) 286331153 / CLOCKS_PER_SEC * rollover_count;


What does 286331153 represent? If we are calculating the number of seconds elapsed during each rollover, then you should be using 2147483647 (2^31 - 1) as the constant (if I typed it in right). You should use the macro LONG_MAX to make the code machine independant.
Would you believe I don't understand myself how this number managed to land in the middle of my code ???:( I remember having used Window's calculator to find out what the maximal value for a 32-bit integer would be, but looks like the Windows calculator is not trustable enough even for things like this o_O

Quote:

(float) 286331153 / CLOCKS_PER_SEC is an inline division of two constants, you may as well precalculate this. The compiler may do this for you, but it would be cleaner to precalculate elsewhere.

good idea, thank you !

Quote:

You may want to replace the data type "float" with "double" because float does not have enough precision to get exact timing conversions in all cases.
alright, you're right again, will do.

Thanks a lot for the review, it's much faster when you get your code reviewed by people who have not spent the day with the nose above it, pointing mistakes is so easy :) Tell me if I can give you a hand in return someday :)

[edit] Where is LONG_MAX defined ? I can't find this macro anywhere...

Pierre-Marie Baty 13-01-2004 17:47

Re: need your advice on clock()
 
Nevermind, found it - in limits.h

Here's the function now.
Code:

float ProcessTime (void)
{
  // this function returns the time in seconds elapsed since the executable process started.
  // The rollover check ensures the program will continue running after clock() will have
  // overflown its integer value (it does so every 24 days or so). With this rollover check
  // we have a lifetime of more than billion years, w00t!
  // thanks to botmeister for the rollover check idea.
 
  static long current_clock;
  static long prev_clock = 0;
  static long rollover_count = 0;
  static double time_in_seconds;
  static double rollover = ((double) LONG_MAX + 1) / CLOCKS_PER_SEC; // fixed, won't move
 
  current_clock = clock (); // get system clock
 
  // has the clock overflown ?
  if (current_clock < prev_clock)
          rollover_count++; // omg, it has, we're running for more than 24 days!
 
  // now convert the time to seconds since last rollover
  time_in_seconds = (double) current_clock / CLOCKS_PER_SEC; // convert clock to seconds
 
  prev_clock = current_clock; // keep track of current time for future calls of this function
 
  // and return the time in seconds, adding the overflow differences if necessary.
  return (time_in_seconds + rollover * rollover_count);
}


botmeister 13-01-2004 20:03

Re: need your advice on clock()
 
Looks much nicer now. You're down to 1 division, one multiplication and one addition per call.

Just one last thing to suggest, since rollovers occure so infrequently, there's a lot of wasted cpu cycles recalcualting rollover * rollover_count for each function call. I suggest that you store the result as static local and update it only after each rollover event.

Quote:

Tell me if I can give you a hand in return someday


You've already helped me several times now!!! I'm just trying to keep pace with you :)

dav 14-01-2004 20:23

Re: need your advice on clock()
 
one other thing - is current_clock, etc. ever negative? it doesn't seem so, therefore why not make those values unsigned longs instead of just longs, and thus get 2^32-1 clock ticks till rollover instead of 2^31-1?

second thing, the return value should probably be a double if you are passing back time_in_seconds, which is a double.

Pierre-Marie Baty 14-01-2004 20:31

Re: need your advice on clock()
 
clock() does not return unsigned longs, hence the rollover is fixed to LONG_MAX and can't be changed. It's the RETURN TYPE of the clock() function which determines the rollover, not the capacity of the variable I put it in.

And in this function the type "double" was used only to provide a greater floating-point computation accuracy, but this extra precision is not needed in the return type, since converting the results to float do not truncate any data at all. The precision was only needed during the computations.

dav 14-01-2004 20:34

Re: need your advice on clock()
 
gotcha. it's too bad clock() doesn't take advantage of the full range of longs.

botmeister 15-01-2004 06:28

Re: need your advice on clock()
 
Quote:

Originally Posted by dav
one other thing - is current_clock, etc. ever negative? it doesn't seem so, therefore why not make those values unsigned longs instead of just longs, and thus get 2^32-1 clock ticks till rollover instead of 2^31-1?

second thing, the return value should probably be a double if you are passing back time_in_seconds, which is a double.

As PMB says, clock() returns a signed long. The only reason for it that I can see, is that it will return -1 if the clock() result is unavailable for whatever reason. Seems silly when they could of had it start from 1 to 2^32-1 and used 0 as the invalid result, or used another method.

In any case, I think the function we now have (once float is replaced with double and all optimizations are put in place) is about as good as it can get.

*edit*

I guess we should have the function check for the -1 result if clock() ever returned it

botmeister 19-01-2004 02:44

Re: need your advice on clock()
 
I'm having concerns about the accuracy of clock(). It may not have enough resolution to do adequate timings in some cases even for a bot. I'll report what I find here once I'm done testing.

BTW to test the ProcessTime function, you don't have to wait 28 days for a rollover, instead create a function called clock_test() and replace clock() with it as shown here

Code:

long clock_test(void)
{
static long counter = LONG_MAX-2; // set this to rollover in 2 clock ticks;
counter++;
if (counter < 0) counter = 0; // reset to zero on rollover;
return counter;
}

Now run a simulted timing test to see if ProcessTime deals with the rollover properly.

Code:

start = ProcessTime(); // clock_test()= LONG_MAX-1
stop = ProcessTime(); // clock_test()= LONG_MAX
 
diff = stop - start; // diff should = 0.001 seconds
 
stop = ProcessTime(); // clock_test()= 0 due to rollover
 
diff = stop - start; // diff should = 0.002 seconds

*update*

On my computer, tests show that clock() increments in units of 10 ticks. This means the actual resolution is in units of 0.01 second instead of 0.001 second. You can still time events which take more time than 1/100th of a second, but there are many timing applications where you'll need more resolution. For example, you cannot use clock() to accurately time the duration of frames (100 FPS = 0.01 seconds per frame which is at the resolution limit).

@$3.1415rin 19-01-2004 20:18

Re: need your advice on clock()
 
already tried using this rdtsc stuff instead ? ok, you'll get the absolute time and not the time of your process, but maybe one could do some filtering ...

why do you need a better timebase than the one of HL ? are the bots running better if calculating your own time ? I always thought that if the bot and the engine are based on the same time, that shouldnt make problem ... but looks like I'm wrong here :) hl is getting stranger and stranger ... but hey, that was always the case, right ?

botmeister 19-01-2004 22:33

Re: need your advice on clock()
 
Quote:

Originally Posted by @$3.1415rin
already tried using this rdtsc stuff instead ? ok, you'll get the absolute time and not the time of your process, but maybe one could do some filtering ...

I almost forgot about rdtsc, thanks for reminding us!

I have not used rdtsc because I am looking for a generic method of timing events. Perhaps I'm wrong, but the rdtsc instruction appears to be cpu specific and not generic from what I read here
http://www.scl.ameslab.gov/Projects/Rabbit/menu4.html

I suppose you could write code to take into account all processors, but that's a lot of effort, and testing it requies access to each cpu type.

note that I'm not actually interested in process time, but in real time, so the performance counter is actually better than the process clock - for me anyway.

Quote:

why do you need a better timebase than the one of HL ? are the bots running better if calculating your own time ? I always thought that if the bot and the engine are based on the same time, that shouldnt make problem ... but looks like I'm wrong here :) hl is getting stranger and stranger ... but hey, that was always the case, right ?
The idea is to create code the is independant of the game engine as much as possible. I think we discussed the reasons why in another thread somewhere, but one reason is ownership (I want to actually own some of the code that I write, not give it away to companies like Valve as per their license agreement), the other is to achieve portability from one game system to another. These are BIG reasons imo to isolate the SDK and the make use of as little of the engine calls as possible.

botmeister 19-10-2004 20:26

Re: need your advice on clock()
 
During my port of mEAn to linux g++ 3.x I discovered that clock() under linux works differently than under windows and returns the processor time (in ticks) used by the calling process. Under windows, clock() instead returns the time (in ticks) that has elapsed since the calling process was invoked.

So, under linux, clock() will tell you how much cpu time the process has used, but under windows clock() will tell you how much time has gone by since the process was first executed. These are two very different numbers, and under linux the use of clock is no good for replacing pfnTime( ).

@$3.1415rin 19-10-2004 20:41

Re: need your advice on clock()
 
clock under windows < me also is only accurate to apprx 100ms I think, cause that's the timebase it's working with. xp / 2k / nt use 10ms, though they are still based on the timer on your mainboard ( system timer is at 1193182 Hz ) and not on the cpu clock.


All times are GMT +2. The time now is 12:32.

Powered by vBulletin® Version 3.8.2
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.