New yield behaviour?
Kitty Cat

I just saw this as listed for the 4.1.13 changes:

Quote:

Changed the behavior of yield_timeslice() to sleeping instead of just yielding.

This is a bad idea, IMO. This forces your program to rest even though it may not need to. For instance, in games where you're trying to get the most FPS possible, you'll still want to "play nice with multitasking" by yielding the CPU, but you still want as much of the CPU as possible (this is important when you're timing things). But now, you're forced to wait even if you don't need to, which introduces lag in programs that want precise timing (as precise as possible, anyway). It's been mentioned that you don't use yield_timeslice to give away the CPU, you use it to yield the CPU while you're waiting.

As an alternative.. what about changing yield_timeslice back how it was and making a new function that's like Sleep() in Windows? Where passing 0 would behave like 4.1.12 yield_timeslice, and passing 1 would behave as 4.1.13 yield_timeslice, ect?

PS: I'm posting this here because I'm not on the A4 dev list, and I don't really have time right now to join up just to say this. Besides, posting it here usually gets it through there anyway. And also, I'd like you guys' thoughts too. I personally think this was a bad move (making it force a wait, instead of only when needed), what about you?

Bob

From the mailing list:

Quote:

Eric:
>>Apply your initial patch to mainline. The current yield_timeslice is pretty
>> useless for the average Allegro programmer and we strive for ease-of-use.
>> Moreover, there is a precedent with BeOS, QNX and MacOS X.
>>

Elias:
Exactly what I was thinking. (And we can always later add the al_sleep
anyway, with al_sleep(0) doing what the current yield_timeslice does,
for the small amount of users who actually need it.)

A J

if its sleep its not yield, so the name is misleading. why would anyone want a function that does not perform its name ?

Elias

You can see the discussion on the dev list here: [url http://alleg.sf.net/maillist.html] - click on [AD], then search for "al_sleep" (if it is working for you - else go to December 2003). I posted a patch which added a new function al_sleep(), leaving yield_timeslice untouched. But the part Bob quoted is why we then preferred to just change yield_timeslice.. most existing code already using yield_timeslice improves by the change, and on a couple of platforms it already worked like it does now anyway. It's purpose is also explained in the docs, so the name yield_timeslice is fully valid.

To get the old behavior back is of course tricky now - you have to do it in a system dependent way. I don't think there are many circumstances where you'd want that though - a vast majority of Allegro programs currently isn't using yield_timeslice at all. And in most of the ones using it, it got added to not waste unnecessary CPU power - what it never did, but does now.

A J

so we let the past dictate the future ?

Quote:

Moreover, there is a precedent with BeOS, QNX and MacOS X.

so a minority of systems, implemented incorrectly (probably due to someone just deciding sleep was 'good enough') can change the meaning of words?

Again i repeat "why would anyone want a function that does not perform its name ?"

if something has to be broken to get it right, then it should be done ASAP.

Kitty Cat

Okay, I can understand this reasoning, that the majority of games designed that use yield_timeslice do so in a fashion to limit CPU usage, not to have the CPU just play nice.

However, I think this should've been handled by adding a new function to give up (as opposed to yielding) the CPU, instead of changing the current function (who's usage has been debated before, so it only adds to the confusion), and then adding another one later on to get the old functionality back.

BTW, wouldn't it have been simpler to just update the rest() function so that it maps directly to Sleep() in Windows, and the equivilants for other platforms? Though I don't know if Sleep(), and it's alikes, affect the whole process or just the thread it's called on.. for the former, I could see some problems if rest() was used in timers (which it shouldn't be), and interfering other threads..

But shouldn't something like this have been better to add, instead of changing yield_timeslice()'s behavior?

void al_sleep(int msecs)
{
#ifdef ALLEGRO_WINDOWS
  Sleep(msecs);
#elif defined ALLEGRO_UNIX // maybe just Linux?
  struct timeval tv = { 0, msecs*1000 };
  select(0, NULL, NULL, NULL, &tv);
#else
  // don't know how to handle it on this platform
  // just use
  rest(msecs);
#endif
}

Elias
Quote:

so we let the past dictate the future ?

Well, it just showed that the exact behavior of yield_timeslice wasn't clear at all. I see the 4.1.13 solution oriented more towards users (i.e. already written code keeps working or improves), than to enforce the original technical specification - which wasn't really clear anyway and didn't make sense to most. The implementation on some platforms just shows that it didn't make sense - users complained about the high CPU usage e.g. for the BeOS port, so Angelo adjusted yield_timeslice to make Allegro play nicely and only use CPU when it is needed. (So e.g. fans make less noise, or batteries last longer. Just yielding wouldn't do that, especially since yielding is done automatically by the preemtying scheduler in some OSes I think.)

Quote:

void al_sleep(int msecs)

That's exactly what my al_sleep was doing. And al_sleep(0) would have done what the yield_timeslice() in 4.1.13 does: Sleep the shortest possible time to have the CPU released. yield_timeslice wouldn't have changed on the main platforms.

A J
Quote:

exact behavior of yield_timeslice wasn't clear at all

void yield_timeslice(); On systems that support this, gives up the rest of the current scheduler timeslice. Also known as the "play nice with multitasking" option.

i found this in alg 4.1.9 and think it explains itself well. it doesn't mention CPU usage, as its not a CPU related function, its a scheduling related function. It explains exactly what the function name describes. its perfect!

Quote:

or improves

sorry, but a sleep does not improve performace at all, instead it now gives you less CPU.
as apps that use allegro tend to be games, they typically want as much CPU as possible.

i think al_sleep() needs to be implemented ASAP.
and yield_timeslice() should do exactly what its name says and not something else.

X-G

A J, you do realize that on most if not all platforms Allegro supports, al_sleep(0) and yield_timeslice() would do the same thing?

gillius

I agree strongly with AJ. yield_timeslice should not sleep for any amount of time. It should perform a yield and nothing more than that. If the current platform does not support yielding, then yield_timeslice should do nothing.

To make it sleep for a period of time makes it go against not only its old behaviour but to go against its name, that any programmer familiar with multithreading will instantly know what a "yield" function will do, and that yield is most definitely different from sleep.

Edit: I'm confused.

In Windows, Sleep(0) yields your timeslice, but doesn't actually sleep. In Windows, does yield_timeslice do a Sleep(0) or a Sleep(x) where x > 0?

Elias

Previously, it was Sleep(0), now it is Sleep(1) in 4.1.13.

The advantage is, code previously sitting in a loop executing millions of yield_timeslice() calls will now not eat up all the CPU. In fact, it will only call it once per timeslice in Windows and most Linuxes, using effectively no CPU at all. So this is very good to have.

On the other side, code who wants to use as much CPU as possible, and still wants other apps to run, needs yield_timeslice. (Actually - I don't quite understand why.. some schedulers obviously can do this automatically. In fact, I always thought preemptying does just that..)

Anyway, just providing an al_sleep() isn't a solution.. since there's no standard minimum time you have to sleep to drop CPU usage. OTOH, Allegro has no way to wait for events - so we need a way to sit in a loop, and have callsto wait for this system specific minimum time. In 4.1.13, yield_timeslice() was modified to be the function to do this, since it was already used like that in most code using it. My original idea was to have al_sleep, and make al_sleep(0) do this.

Kitty Cat

Forget it. My g.d. browser crashed two times while I was trying to write a lengthy reply. >:(>:(

Basically, I agree with what A J is saying. yield_timeslice says nothing about CPU usage. And yielding to the timeslice scheduler is different than resting the program (although resting the program implies a timeslice yield, it does more than that). IMHO, al_sleep should map directly to Sleep (al_sleep(0) -> Sleep(0)), or equivilant for the platform. Yeah, it'll be more work for the developers having to change yield_timeslice() to al_sleep(x) and d_yield_proc to d_sleep_proc, but the focus should be on end-user programmers. IMHO.

CGamesPlay

Also with 4.1.13, there is a disabling of screensavers? Can you, like, take that out until you can make an API call to turn it off? I'm trying to make a utility right now, not to mention many other apps I've written using Allegro.

[edit]
Also

docs/build/msvc.txt said:

When using a statically linked library, you must define the preprocessor
symbol ALLEGRO_STATICLINK before including any of the Allegro headers and
link your program against Allegro and the main Win32/DirectX libraries
in that order

As it's MSVC, the order isn't necessary. And unless something changed, you don't need to do the that at all (link in those libs).

A J

what topic are you on CGamesPlay ?

back to the yield issue..

Quote:

he advantage is, code previously sitting in a loop executing millions of yield_timeslice() calls will now not eat up all the CPU.

lets say :

while ( game_loop )
{
   do_game();
   yeild_timeslice();
}

the old way (Sleep(0)/real yeilding) would work just fine, giving the OS a chance to do its thang if it needed. however the new way, Sleep(>0), would give you less CPU. lets say your do_game() takes 30ms to process a frame, thats 1000/30=33fps, which is good! but the yeild now sleeps(1) on Win32 which actually sleeps closer to 10ms.. you have 30ms/frame + 10ms sleep = 40ms/frame = 25fps.. so now your app has just lost 8fps. thats like 25% performace decrease.

Bob
Quote:

so now your app has just lost 8fps. thats like 25% performace decrease.

And if the OS actually had things to do in the background, then you'd still run slower. Point is - if you don't want to let go of the CPU because you're in some time-critical portion of the code, then don't call yield_timeslice()!

A J

on win32, a search on the allegro src code failed to find timeBeginPeriod() so i presume a Sleep(1) actually does a sleep(10).
if this is the case, we have another problem.

bob, i think your avatars frown is warranted. ;)

Kitty Cat
Quote:

if you don't want to let go of the CPU because you're in some time-critical portion of the code, then don't call yield_timeslice()!

I agree with this. However, yield_timeslice is letting go of the CPU for too long, now. If you're in a waiting loop, you have some flexibility. If it's alright that you don't get back immediately, then sleeping is fine, but when things are a bit more critical in a waiting loop, where you'll want to return asap in case you need to break, you'd use yield_timeslice.

while(playing)
{
  while(waiting_for_frame_tic)
    yield_timeslice();

  do_frame();
}

Originally, this would return immediately if nothing is waiting, or it would let the other programs run, and then it would get back. But now, this sleeps a minimum of ~10ms regardless of what else is going on.

It just seems to me that making yield_timeslice() sleep and relinquish the CPU is just a hack to lower CPU usage. But with proper usage of yield_timeslice, other programs wouldn't suffer any less than if you force giving the CPU away. And the only real reason to lower CPU usage is for laptops and power consumption, and if that was the case, why not make Allegro startup a thread that periodically calls Sleep/al_sleep(1) to slow the process? Then it wouldn't rely on the user structuring his program so that it would have to loop a call to anything.

Although, even if this change should've been done, I think al_sleep() should've been implemented first, or at least at the same time. Then the dev team could've told us to change our programs to match the new behavior (so we could get the old behavior back). But for now, we're stuck with how it is, until al_sleep is implemented.

Elias
Quote:

It just seems to me that making yield_timeslice() sleep and relinquish the CPU is just a hack to lower CPU usage. But with proper usage of yield_timeslice, other programs wouldn't suffer any less than if you force giving the CPU away. And the only real reason to lower CPU usage is for laptops and power consumption, and if that was the case, why not make Allegro startup a thread that periodically calls Sleep/al_sleep(1) to slow the process? Then it wouldn't rely on the user structuring his program so that it would have to loop a call to anything.

It is a bit hackish, yes - ideally your code example would tell the OS to get back to the program in the moment the timer is changed. But with Allegro's architecture, this doesn't seem easy. And I assume, such a waiting function would have the same 10ms accuracy problem, at least in Windows and some Linux. Running another thread to call Sleep() would just make that thread sleep, so wouldn't help at all. And slowing down Allegro in any way is not what we want - just the possibility to give up CPU time, but only if the user wants to do so.

About the power consumption.. I expect Doom 3 to take 100% CPU. But if I start lxdoom here, it takes only a few % of CPU - which makes sense, given it could run on a <100MHz computer with full framerate. My platformer who runs on a Pentium-200 with its maximum 60 FPS is the same.. it simply should only take about 1/5th of the CPU on a processor 5 times as fast. And any application made with Allegro taking CPU while idle just feels wrong all the same. The CPU heating up also can have ugly side effects, like fans rotating faster and causing too much noise to hear the sound in the game :P

So, I don't see a way around a function which does what the 4.1.13 yield_timeslice does. al_sleep(1) alone wouldn't be so good, since we don't know how the scheduler works on the current platfoem, and what exactly it does in response to a 1ms sleep. (But al_sleep(0) could work.) The question is just, if you have code which is so time critical, why would you call yield_timeslice() at all? Just run your game which takes 100% CPU.. there's no OS which will stop responding to user input because of that (I hope). And for some special uses, you'd need a platform specific solution.

In any case, I don't mind al_sleep(), with al_sleep(0) doing what 4.1.13 yield_timeslice() does. And al_sleep(x) just calling Sleep(x).

Kitty Cat
Quote:

In any case, I don't mind al_sleep(), with al_sleep(0) doing what 4.1.13 yield_timeslice() does. And al_sleep(x) just calling Sleep(x).

But again, this feels hackish. As you said, you don't know how the scheduler works, and that would make the behavior of al_sleep(0) indeterminate. What about something like this, then:

yield_timeslice() -> Yield to the scheduler, and return ASAP. Don't rest.
al_sleep(x) -> rest the thread at OS level (if possible), else just call rest(x).
al_idle() -> return the CPU to the OS (hopefully reducing CPU usage), and wait.
             Time spent idling would depend on the preciseness of the OS, current
             time in the slice, other processes' CPU usage, ect.

Elias

Yes, I meant, al_sleep(0) would do what your al_idle() does. Maybe we don't need al_sleep at all, and just al_idle.

EDIT: rest() can already be used to wait a speceific amount of time. And rest() than would call al_idle() instead of yield_timeslice() as it does currently.

Kitty Cat
Quote:

rest() can already be used to wait a speceific amount of time. And rest() than would call al_idle() instead of yield_timeslice() as it does currently.

That sounds good to me. Though then rest() would need a way of timing al_idle to make sure enough time has passed, since there'd be no gaurantee of how long al_idle will wait. I timing interrupt/thread would be a Very Bad Idea for this, IMO.

EDIT:
Using clock as an example (OS-specific calls should be used.. (u)clock isn't reliable in DOS once install_timer() is called):

void rest(int msecs)
{
  clock_t start = clock();

  while((start - clock()) / (CLOCKS_PER_SEC / 1000) < msecs)
    al_idle();
}

Would also be a good incentive for a al_get_msecs() function..

Elias

Yes, I meant just that, rest() could use al_idle() to not do busy waiting.

And yeah, in order to not use a timer "interrupt" for timing, we'd need al_get_msecs() - which has been on my wishlist for a while as well. Except for DOS, al_get_msecs() could simply call the corresponding OS function. In DOS, we'd of course keep using the timer, since it's 100% accurate there anyway.

A J

lets not have a function with an abstract meaning.
al_idle() could mean anything.. what is idle ?
its too abstract; and there is the problem.

lets just have yield_timeslice() that does exactly what its name implies.

CGamesPlay

The second part of my post is just something I didn't notice before in the docs. The first part is something I know is new to 4.1.13, and since this is a thread about 4.1.13, I thought this would be a good place for it.

Kitty Cat
Quote:

lets not have a function with an abstract meaning.
al_idle() could mean anything.. what is idle ?

It idles the Allegro program.. where as yield_timeslice would, correctly, only yield the current timeslice if needed, al_idle would idle the program so all other processes (including those with idle priority, like the system idle process in Windows) get some time, whether they need it or not.

Richard Phipps

Looking at it from a game perspective, we will have a timer setup which we use to syncronise the logic updates and the gfx updates. The timer will be set to update x times per second.

So we will have something like this (based on how I do the inner loop):

1do
2{
3 // Do we need to do a logic update?
4 if (ticky.tick > 0)
5 {
6 do
7 {
8 // Do x amount of logic updates..
9 do_all_logic_work();
10 ticky.tick--; // Reduce timer
11 } while (ticky.tick > 0);
12
13 // Ok, we did a logic frame (or more to compensate for skipped frames)
14 // Now let's draw the new gfx.
15 do_all_gfx_work();
16 }
17} while (done == NO);

That's something similar to what we would normally do. But if our game was only using a small amount of the CPU time then what we would really want is :

1do
2{
3 // Do we need to do a logic update?
4 if (ticky.tick > 0)
5 {
6 do
7 {
8 // Do x amount of logic updates..
9 do_all_logic_work();
10 ticky.tick--; // Reduce timer
11 } while (ticky.tick > 0);
12
13 // Ok, we did a logic frame (or more to compensate for skipped frames)
14 // Now let's draw the new gfx.
15 do_all_gfx_work();
16 }
17 else
18 {
19 // We are waiting for the new frame..
20 al_wait_frame(ticky.tick);
21 }
22} while (done == NO);

Where al_wait_frame would give up time to the OS until ticky.tick changed.

I don't know if such precision is possible, but for games I think that's what I would like to be able to do.

Kitty Cat

For FPS-locked games, even if the FPS lock is around 60 or 70, sleeping even 10ms can be a detriment to fluid frame rates, especially on machines marginally able to achieve the full frame rate. The problem is that giving up the CPU, as opposed to just yielding the timeslice, will take longer before you get it back, especially with other processes running.so, by the time you call al_idle or yield_timeslice, you don't know whether you need to come back immediately (since the game may have ticked right after you last checked), or if you still have a bit yet to wait. Which is why for time-sensitive wait loops, you still want the CPUs attention (which you lose with al_idle), but you don't necessarilly need to hog it from the other processes. So, a setup like this would work:

while(real_tic > current_tic)
{
  do_game_tic();
  ++current_tic;
}

draw_frame();

if(real_tic == current_tic)
{
  if(lower_cpu_usage)
    al_idle();

  while(real_tic == current_tic)
    yield_timeslice();
}

That way, for systems fast enough, you can set lower_cpu_usage, and al_idle would get called regularly, which would be enough to drop CPU usage, but the wait-loop would still be attentative with yield_timeslice. And of course, for slower machines, lower_cpu_usage would be false so even if it ends up waiting, it wouldn't idle and end up waiting too long on accident. And all this while playing nice with the other programs running.

Trezker

My code looks like this.

while(!key[KEY_ESC]){
   ntick=ticks;
   ticks=0;
   for(n=0;n<ntick;n++){
      //update
   }
   //draw
   yield_timeslice();
}

Bob

Here's a suggestion: Make yield_timeslice() do the following():

- Create a 10ms timer.
- Get a handle to the install_int() timer
- Call WaitForMultipleObjects() on those.

This way, the scheduler will hand back contorl of the CPU when an Allegro timer tick occurs, or 10ms elapses, whichever comes last.

Trezker

I haven't put the function through any real testing, but I know it works fine as it was before.

Two locally connected network test windows using libnet worked perfectly with a call to yield_timeslice, while without it the window without focus lagged horribly...

I'd like to know what can be gained.
Also, I have no idea what the function actually does...::)

Kitty Cat

But yield_timeslice shouldn't wait at all if nothing else needs the timeslice. When you yield to something, you only stop when something else is coming (to take the next slice). Having that functionality for al_idle could work though, if that's an efficient way of dropping CPU usage without losing much responsiveness (it looks a bit.. convoluted, to me..).

Elias
Quote:

Here's a suggestion: Make yield_timeslice() do the following():

- Create a 10ms timer.
- Get a handle to the install_int() timer
- Call WaitForMultipleObjects() on those.

Will that be less prone to the 10ms inaccuracy than the 4.1.13 yield_timeslice? If so, it looks like a good idea. Just the timer API would need to be modified to allow return a handle. I'm assuming something like:

handle = install_int_ex (...);
yield_timeslice (handle);

So now, yield_timeslice returns as soon as either the 10 ms timer it creates finishes, or the timer who'se handle was passed is called. Of course, this only makes sense in case WaitForMultipleObjects can return sooner than 10 ms. select (which I guess is sort of the unix equivalent of that function) will in most implementations return after a time which is a multiple of the timeslice - just what the current 4.1.13 yield_timeslice does already.

gillius

install_int_ex is not compatible with that because it already has a return value where 0 is not the error code. You'll have to make a new function that returns the handle (or 0 if the handle is invalid), or make the function return an error code and take a pointer to a handle.

EDIT: actually I realized this is pointless. The timers work based on sleeping I believe, or at least based off that "hidden" 10ms timer because they are blocked waiting for time to pass. Therefore yield_timeslice won't return any sooner because the timer can't fire faster than 10ms. At best, you will give the main thread higher priority than the timer, which is bad, because you want to see the timer count increment (so you can start a new frame). Using WaitForObject will always make the main thread run before the timer, so the main thread will still see its count be zero then either busy-wait, wasting the whole timeslice, or calling yield_timeslice, being 10ms late.

This is a good reason why not to use Allegro timers. If we want less than 100% CPU, doing timing based on multithreaded timers like this is a very bad idea.

And again, we are giving yield sleeping semantics instead of yielding semantics.

Bob

Of course, if you can argue for it on [AD], it can be changed back.

Thread #338662. Printed from Allegro.cc