views:

542

answers:

5

I've written a C++ library that does some seriously heavy CPU work (all of it math and calculations) and if left to its own devices, will easily consume 100% of all available CPU resources (it's also multithreaded to the number of available logical cores on the machine).

As such, I have a callback inside the main calculation loop that software using the library is supposed to call:

while(true)
{
    //do math here
    callback(percent_complete);
}

In the callback, the client calls Sleep(x) to slow down the thread.

Originally, the clientside code was a fixed Sleep(100) call, but this led to bad unreliable performance because some machines finish the math faster than others, but the sleep is the same on all machines. So now the client checks the system time, and if more than 1 second has passed (which == several iterations), it will sleep for half a second.

Is this an acceptable way of slowing down a thread? Should I be using a semaphore/mutex instead of Sleep() in order to maximize performance? Is sleeping x milliseconds for each 1 second of processing work fine or is there something wrong that I'm not noticing?

The reason I ask is that the machine still gets heavily bogged down even though taskman shows the process taking up ~10% of the CPU. I've already explored hard disk and memory contention to no avail, so now I'm wondering if the way I'm slowing down the thread is causing this problem.

Thanks!

+21  A: 

Why don't you use a lower priority for the calculation threads? That will ensure other threads are scheduled while allowing your calculation threads to run as fast as possible if no other threads need to run.

Brian Rasmussen
In practice, that's not very reliable on Windows.I've ported the library to OS X and there it works like a dream with a lower priority, but on Windows it'll still cause problems.Also something else: for marketing/tech-support purposes we find it a bad idea to have the software ever take up over 20% of the CPU in the taskmanager, or else you get some crazy complaints and outlandish reviews about your software killing PC performance. People see 50% CPU usage and panic!
Computer Guru
I can't say why your port behaved liked that. In my experience the scheduling works well on Windows. In any case, given the question I believe it is best solved using priorities and leaving the scheduling to the OS.
Brian Rasmussen
Computer Guru, thread priorities work quite reliably on Windows. However, if you're doing a lot of I/O (it doesn't matter if it's direct or caused by memory paging), you might want to reduce your I/O priority as well. See SetThreadPriority and THREAD_MODE_BACKGROUND_BEGIN.
avakar
Thanks, avakar. I hadn't thought of that - there is indeed quite a lot of reading and writing to files involved (though it's buffered in 4MB chunks now, I'm not sure if it was when I first tried to lower the thread priority), and plenty of memory paging (which is another problem in and of itself).I'll give that a shot :)
Computer Guru
One possible difference is that Mac hardware maxes out more gracefully, while many people can't sit for very long next to their fan going full blast.
Potatoswatter
+2  A: 

Sleep should be fine for throttling an app, which from your comments is what you're after. Perhaps you just need to be more precise how long you sleep for.

The only software in which I use a feature like this is the BOINC client. I don't know what mechanism it uses, but it's open-source and multi-platform, so help yourself.

It has a configuration option ("limit CPU use to X%"). The way I'd expect to implement that is to use platform-dependent APIs like clock() or GetSystemTimes(), and compare processor time against elapsed wall clock time. Do a bit of real work, check whether you're over or under par, and if you're over par sleep for a while to get back under.

The BOINC client plays nicely with priorities, and doesn't cause any performance issues for other apps even at 100% max CPU. The reason I use the throttle it is that otherwise, the client runs the CPU flat-out all the time, and drives up the fan speed and noise. So I run it at the level where the fan stays quiet. With better cooling maybe I wouldn't need it :-)

Steve Jessop
On Linux, if you run your process with a higher nice level, the OS will keep the CPU clock low, assuming your hardware supports CPU speed scaling. This will keep the power, heat, and noise low without artificial throttling.
karunski
Sweet. Switching entirely to linux is an option I constantly consider and so far always reject...
Steve Jessop
A: 

Have a look at cpulimit. It sends SIGSTOP and SIGCONT as required to keep a process below a given CPU usage percentage.

Even still, WTF at "crazy complaints and outlandish reviews about your software killing PC performance". I'd be more likely to complain that your software was slow and not making the best use of my hardware, but I'm not your customer.

Edit: on Windows, SuspendThread() and ResumeThread() can probably produce similar behaviour.

P-Nuts
A: 

Another, not so elaborate, method could be to time one iteration and let the thread sleep for (x * t) milliseconds before the next iteration where t is the millisecond time for one iteration and x is the choosen sleep time fraction (between 0 and 1).

Peter Jansson
+3  A: 

What is wrong with the CPU at 100%? That's what you should strive for, not try to avoid. These math calculations are important, no? Unless you're trying to avoid hogging some other resource not explicitly managed by the OS (a mutex, the disk, etc) and used by the main thread, generally trying to slow your thread down is a bad idea. What about on multicore systems (which almost all systems will be, going forward)? You'd be slowing down a thread for absolutely no reason.

The OS has a concept of a thread quantum. It will take care of ensuring that no important thread on your system is starved. And, as I mentioned, on multicore systems spiking one thread on one CPU does not hurt performance for other threads on other cores at all.

I also see in another comment that this thread is also doing a lot of disk I/O - these operations will already cause your thread to yield while it's waiting for the results, so the sleeps will do nothing.

In general, if you're calling Sleep(x), there is something wrong/lazy with your design, and if x==0, you're opening yourself up to live locks (the thread calling Sleep(0) can actually be rescheduled immediately, making it a noop).

Terry Mahaffey
Perhaps a bit of context will help: the math is to calculate the minimal difference between two files for a backup program. A "set it and forget it" backup program should not take 100% CPU, while a single-purpose "file diff" program should. Therein lies the key to this design :)
Computer Guru
Ah, I see; that is exactly why process and IO priorities were invented. You should do that and let the OS scheduler take care of things. Skip the Sleeps.
Terry Mahaffey
I think Computer Guru is saying that his backup program shouldn't take 100% CPU, *even if the system is otherwise completely idle*. karunski says that linux achieves this just by assigning a sufficiently low priority, but it's not what priority in POSIX is defined to mean. There's a difference between relative priority among processes ("don't do this fast if it would starve something important") and what I guess you could call absolute priority ("don't do this fast, ever").
Steve Jessop