Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » The good and the bad...loop

This thread is locked; no one can reply to it. rss feed Print
The good and the bad...loop
#00JMP00
Member #14,740
November 2012

In the past time I read much about loops consuming full cpu times vs. others. Can someone explain the difference between both and give an example, which can be understood??
Actually theres is no necessity consuming more cpu time than the program requires.

Arthur Kalliokoski
Second in Command
February 2005
avatar

Let's assume your program does 100 frames per second. Wallow in simplicity.

#SelectExpand
1while(1) 2{ 3 double curr_time = get_time(); //Let's say it got 32.056 seconds 4 double next_time = curr_time + 0.01; //This is when the next loop should start, 32.066 seconds to be specific 5 6 get_input(); //keyboard, mouse, joystick, whatever. Let's say that it took 0.000053 seconds, so current time 7 //is now 32.056053 (although it didn't measure it) 8 9 calculate_new_positions(); //logic and physics. 0.00002 seconds. Now current time is 32.056073 10 render_scene(); //update screen. 0.0015 seconds. Now current time is 32.057573 11 12 while(2) 13 { 14 curr_time = get_time(); //First time through *here* takes 0.00001 second, so it returns 32.057573 15 if(curr_time >= next_time) 16 { 17 break; 18 } 19 } //Loop back to beginning, since we're not at 32.066 seconds yet, and since it only takes 0.00001 second, 20 //it'll loop about 842 times until time is up, burning CPU all the while. 21 22 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 24 //Now replace the above while(2) loop with this 25 curr_time = get_time(); //Once again it takes 0.00001 second, so it returns 32.057573 26 double deltatime = next_time - curr_time; //32.066 - 32.057573 = 0.008427 27 sleep(deltatime); //Tell the OS not to schedule us again for this long. 28}

Now the OS also has its own timers. If it runs though all the programs in the list and it has time left over, it puts the CPU itself to sleep, only to be awakened by a hardware timer chip, then it runs through all the programs, giving them some small sliver of CPU time as requested. But if your program is in a sleep(), and doesn't need any time, it just skips over it to the next program, which enables it to put the CPU into halt mode (sleep) that much sooner, resulting in lower temperatures and electrical usage.

[EDIT]

I should have stated that last a little more clearly. If 50 programs are running, and none of them want to sleep(), then each one will get 1/50th of an OS tick. If 49 of them want to sleep(), then the last remaining program will get the entire time slice, resulting in 100% CPU for that program. Only if they all ask for some sleep() time will CPU usage be less than 100%. Disk access and other things that block may do this as well as sleep().

“Throughout history, poverty is the normal condition of man. Advances which permit this norm to be exceeded — here and there, now and then — are the work of an extremely small minority, frequently despised, often condemned, and almost always opposed by all right-thinking people. Whenever this tiny minority is kept from creating, or (as sometimes happens) is driven out of a society, the people then slip back into abject poverty. This is known as "bad luck.”

― Robert A. Heinlein

Peter Wang
Member #23
April 2000

Another explanation. In a multitasking operating system, each process/thread is in a certain state (http://en.wikipedia.org/wiki/Process_state). In the 'Waiting' state the OS will give a share of the available CPU time to your thread, moving it to the 'Running' state. Once that time slice is exhausted (e.g. 10 ms), the thread moves back into the 'Waiting' state, and another process/thread is allowed to execute.

while (!key[KEY_ESC]) { /* do nothing */ }

This is called a busy wait. Without any other information, you are asking the OS to give you as much CPU time as possible. Yet during that time all you are doing is checking if a global variable was changed - in this case by a Allegro 4 background thread, which could only change the global variable once it gets a chance to execute. It won't be able to execute on the same core while you are occupying it either, so very inefficient.

For better use of the CPU, you need to put the process/thread into the 'Blocked' state until you can make progress. An imperfect way to do that is to give up the rest of your time slice and not execute again until some amount of time has passed. This can be done, for example, with Allegro's rest and al_rest calls.

while (!key[KEY_ESC]) {
   rest(1);
}

This is imperfect because your thread still has to be run every so often to check a global variable, probably somewhere between 100 and 1000 times per second. At least you are no longer busy waiting, but not as good as it could be.

The better solution is to put your thread into the 'Blocked' state for as long as possible, but no longer, otherwise you will react too slowly when something does change. Various function calls will do this. In Allegro 5 the principal call which does so is al_wait_for_event. Your thread will be 'Blocked' and only be run again until there is something in the event queue:

while (!quit) {
   al_wait_for_event(queue, &ev); /* thread blocks until queue is non-empty */
   if (ev.type == ALLEGRO_EVENT_KEY_DOWN && ev.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
      quit = true;
   }
}

#00JMP00
Member #14,740
November 2012

"All Hail!"

I feel enlightened now.

Go to: