Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » A Paradox: Sleep(0) Using Less CPU than Sleep(1)???

This thread is locked; no one can reply to it. rss feed Print
A Paradox: Sleep(0) Using Less CPU than Sleep(1)???
Kris Asick
Member #1,424
July 2001

Sorry for not being around the forums lately, life has been hectic and I kinda burned myself out while programming Space Fortress 2. I've since switched to another, smaller project for a change of pace and intend to go back to SF2 once it's finished. I'm also busy looking for a job too, which means once I get one I'll have far less time to work on my shareware. :-/

But, that aside, working on this new project of mine, which is using my own OpenGL wrapper over Allegro 4.2.2, not AllegroGL, I encountered a curious paradox just moments ago...

Calling Sleep(0) in my main loop is using LESS CPU power than calling Sleep(1)!

I was calling Sleep(1) since I started working on my wrapper and a little rotating cube test I've been working on has been burning 6% CPU. Out of curiosity sake, I changed to calling Sleep(0), fully expecting my program's CPU usage to jump to 100%...

...and it went DOWN to 2%! :o

OpenGL clearly must have some non-busy waits built-in or something, but the fact that CPU usage actually went down by releasing less of it is perplexing. The only thing I can imagine is that intentionally releasing CPU power from my main loop is encouraging Allegro's timers and I/O to draw MORE power.

Looks like that idle system I made is kinda useless with OpenGL running the show. ::)

Obviously, this isn't a problem or anything, but for curiosity sake, does anyone have any insight as to what might be happening here?

--- Kris Asick (Gemini)
--- http://www.pixelships.com

james_lohr
Member #1,947
February 2002

Do you mean rest()? Calling rest(0) will just yeild, giving up the CPU, and presumably letting the OS decide when your process gets the CPU back. It makes perfect sense for this to potentially take longer than rest(1) which, I'm guessing, requests the CPU back after ~1 ms (or agrees this upon giving it up as part of the "contract").

Kris Asick
Member #1,424
July 2001

rest() does pretty much the same thing Sleep() does, it just has a little extra coding to make it multi-platform. Sleep() is just the Windows platform-specific command.

rest(0) and Sleep(0) both only give up CPU time to threads of equal priority, only give up the remainders of time slices, not additional time slices, and only do so if other threads need processing, otherwise they do absolutely nothing.

This is why it's strange that Sleep(0) is causing my OpenGL app to use less processing power than Sleep(1). In a normal non-accelerated application, Sleep(0) results in 100% CPU usage. (rest(0) would have the exact same effect.)

--- Kris Asick (Gemini)
--- http://www.pixelships.com

gillius
Member #119
April 2000

It's been a long time, but I wonder if there are certain buffering modes in OpenGL and other accelerated libraries such that if you vsync enabled, the request to show the display blocks until vsync. The 2% vs 6% might just be a measurement issue. If that's the case then the calls to Sleep would not have any effect as long as the loop could draw frames faster than vsync.

Gillius
Gillius's Programming -- https://gillius.org/

Kris Asick
Member #1,424
July 2001

I considered that, and I have my video card set to triple buffer hardware accelerated apps. So I went to go turn that option off and didn't notice a change in CPU use.

...actually, I know triple buffering rarely works in windowed mode on just about every computer out there, if any, so I don't think that was going to be a factor.

...wait... *goes to check vertical sync setting on video card* ...hmm... it's set to "application controlled", and since I'm not setting vsync myself, OpenGL might default to it on my card... I wonder... *forces the card to not vsync and tries again*

There it goes! Now I'm getting about 97% CPU use. So it seems OpenGL vsyncs by default on my video card unless an application says not to. Classy. ::)

Still doesn't explain why CPU use dropped calling Sleep(0) instead of Sleep(1), but at least it explains why it didn't skyrocket: OpenGL does non-busy vsyncing on my video card. Sweet. ;D

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Thomas Fjellstrom
Member #476
June 2000
avatar

Sleep(1) is probably a blocking wait. ie: while(1..10000000) { /* rest */ } while Sleep(0) just optionally gives up a cpu slice. Obviously not using cpu uses less cpu ;)

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Elias
Member #358
May 2000

rest() only gives up any CPU if you installed a timer, else it's just a busy loop taking 100% CPU. Sleep() should be different... but who knows. So the only time you actually let the CPU rest is in OpenGL's vsync wait.

--
"Either help out or stop whining" - Evert

Tobias Dammers
Member #2,604
August 2002
avatar

Sleep(n) is supposed to give up the current time slice and schedule the current task for re-activation after n milliseconds.
Sleep(0) is supposed to just give up the current time slice but otherwise remain active.
I'm no expert, but my guess would be that since 1 ms is probably less than the OS thread scheduler's granularity, both calls are functionally equivalent, but Sleep(1) has to go through the extra effort of putting the thread to sleep and re-scheduling it, with all sorts of OS-level voodoo happening in this thread's CPU time, just to get reactivated on the next time slice anyway.

Sleep(1) is probably a blocking wait.

Yes, but I doubt it is implemented as a busy loop, but rather by asking the OS to not give the current thread any CPU time for the next n milliseconds.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Thomas Fjellstrom
Member #476
June 2000
avatar

You'd hope so, but you never know. I haven't seen the code :P

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Tobias Dammers
Member #2,604
August 2002
avatar

It might have been back in the days of win98, when multi-threading wasn't so commonplace. These days however, implementing sleep() with a busy loop at the winapi level would quickly stall any CPU running more than a small handful of threads. Even if the windows task scheduler isn't as sophisticated as the linux kernel (and seriously, I have no idea whether this is actually the case), I expect it to perform a bit better than to max out the CPU all the time and just not tell the user.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Kris Asick
Member #1,424
July 2001

Thomas is wrong, Tobias is right. :P

I've done a TON of past research into this subject, specifically on the Windows side of things. (Thank you MSDN libraries!) So without further ado...

--------------------------------------------------------------------

A Quick Lesson About Timing in Windows

Windows has three timing methods you can utilize:

  • Thread Scheduler

  • Multimedia Timer

  • High Performance Counter

The simplest timer to use is based on the thread scheduler, and you utilize it each time you call Sleep(). (Just to quickly point out, the Allegro 4.2.x rest() function calls Sleep()!) You can also utilize it by doing event-based timing with WM_TIMER messages. The problem with using the thread scheduler is granularity, which cannot be guaranteed. On some systems it will be an extremely small granularity, such as on my system, but on others it will be extremely large, up to and over 50 msec. When you call Sleep(n) (or use Allegro's rest(n)) it puts your program thread into a non-busy wait and isn't checked again until the next batch of time slices are dished out. So calling Sleep(n) rounds UP to the nearest granularity. This means Sleep(1) can result in non-busy waits of 50 msec or more. If the granularity is 50 on your system and you call Sleep(51), the wait can be 100 msec! And again, Sleep(0) only gives up time-slices when threads of equal priority could use the extra time, which means calling Sleep(0), in it of itself, will NOT free up CPU usage, EVER!

The Multimedia Timer is primarily used to keep video and audio in sync, and is better for gaming than relying on the thread scheduler, but has its own problems. It creates its own thread when you invoke it, which means, depending on thread priorities, it can suffer from delays and latency, plus any variables you create to be used by it need to be properly multi-threaded to prevent your main program and the timer from trying to access said variables simultaneously. Plus, the Multimedia Timer has a granularity of 1 msec. OK for 20 FPS, not too great for 60 FPS, terrible for anything higher on a monitor that can support higher.

The High Performance Counter is the best thing to use for timing, and is what Allegro's timer system is built off of in the Windows branch. It's accurate down to about 0.8 microseconds, depending on your CPU. Using it manually is as easy as declaring a 64-bit integer (LARGE_INTEGER Windows constant) and calling about three different commands. Virtually every computer out there anymore that can run a Windows OS will support this timing system. The catch: this is a COUNTER, not an actual TIMER. This means that while it is extremely accurate, it cannot actually give up unused CPU time like the Thread Scheduler and Multimedia Timers can... not by itself anyways. With the help of multi-threading (or Allegro's timing system) and semaphores, it is very possible to create non-busy waits using the High Performance Counter down to whatever granularity you want.

Lesson Over

--------------------------------------------------------------------

My thoughts about Sleep(0) using less CPU power than Sleep(1) in my OpenGL program revolve around the combination of Allegro with OpenGL, since Allegro is multi-threaded under Windows, generating both a timing thread and an I/O thread in conjunction with the main program thread. (Allegro 4.0.x generated THREE I/O threads, one each for Keyboard, Mouse and Joystick support!) Since the program is using the extra CPU time, this means that during a Sleep(1) call, one of Allegro's threads, either the timer or I/O, is pulling more power for whatever reason. My guess is it would be the I/O thread, since it's equal priority to the main program thread and does not give up any CPU time in it of itself.

It doesn't really matter, since I build all my games to run real-time at the highest FPS a computer's monitor can portray, so I can just enable vsyncing by default in my future OpenGL apps, but it's still a curiosity to call Sleep(0) and end up using less power than calling Sleep(1) when you know a lot about about how timing works under Windows! ;)

Oh... and BTW Tobias, you'd be surprised just how many threads can be going at once on a Windows 9x machine. I'm still running Windows 98 SE and right now, not counting IE6 (since it's using 25 threads right now for some reason) there's 91 threads going! That said, the majority of those threads are either in permanent, non-busy wait loops, waiting for certain messages to tell them to do stuff, or are set to extremely low priority because they only need to keep brief tabs on things.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Thomas Fjellstrom
Member #476
June 2000
avatar

My guess is it would be the I/O thread, since it's equal priority to the main program thread and does not give up any CPU time in it of itself.

It aught to be sleeping as well. Any decent event handler thread will sleep till an event is available.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Kris Asick
Member #1,424
July 2001

It aught to be sleeping as well. Any decent event handler thread will sleep till an event is available.

I'd have to take another look at the Allegro source, but I'm pretty sure the I/O thread never intentionally gives up CPU time.

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Thomas Fjellstrom
Member #476
June 2000
avatar

I'd have to take another look at the Allegro source, but I'm pretty sure the I/O thread never intentionally gives up CPU time.

Not even a WaitForObject or whatever the function is called?

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Ron Novy
Member #6,982
March 2006
avatar

Just some notes...

During some debugging last year I found Sleep(1) to be better then Sleep(0) for applications with multiple running threads. From my experience Sleep(0) sometimes does not give up CPU time to other threads and may just simply return. Sometimes my app would run fine when using Sleep(0) and other times it wouldn't, but when using Sleep(1) to give up CPU time it worked 100% of the time.

----
Oh... Bieber! I thought everyone was chanting Beaver... Now it doesn't make any sense at all. :-/

Kris Asick
Member #1,424
July 2001

*looks through the Allegro source files*

Thomas Fjellstrom: Well... there are some WaitForObject commands in the input handlers... though I also noticed that Allegro sets up a different thread handler for I/O when attaching to a custom Window, since the main I/O thread is usually based off of the Allegro window, and I need to manually attach Allegro to my own custom Window to get OpenGL working with Allegro... so that could be part of it too.

Ron Novy: It's normally best to use Sleep(0) if you need maximum performance, since Sleep(1) could kill your framerate as far down as 20 FPS or further on some computers. If Sleep(0) is causing problems though, and your application is multi-threaded, chances are you're not properly syncing your threads, leading to "run-away" threads, that process and process and process without properly stopping to give up CPU time to other threads in the same app. This is what causes the I/O to go screwy for split-seconds sometimes with Allegro 4.2.x and Windows 9x and is a symptom of multi-threading I discovered myself when trying to write my own I/O handler awhile back. (I've thus elected to detect Windows 9x in my apps now and force Sleep(1) into my loops if detected to prevent this phenomena from happening.)

--- Kris Asick (Gemini)
--- http://www.pixelships.com

Ron Novy
Member #6,982
March 2006
avatar

Arg... You're probably right. The way my program worked was a dispatcher function divided a block of data into sub blocks where each sub block must be processed before the dispatcher could return. There was a single threaded version and a multi-threaded version using the dispatcher funtion to create the sub blocks and create a predetermined number of threads and then call the recursive single threaded version. The multi-threaded version was fast and everything worked perfect except every now and then the dispatcher would get the signal that the processing was already completed. Sleep(1) fixed it, but it ran fine on slower computers so I'm guessing I missed something obvious if Sleep(0) is actually functioning correctly...

----
Oh... Bieber! I thought everyone was chanting Beaver... Now it doesn't make any sense at all. :-/

Go to: