Re: System resources and number of operations on an NSOperationQueue
Re: System resources and number of operations on an NSOperationQueue
- Subject: Re: System resources and number of operations on an NSOperationQueue
- From: Ken Thomases <email@hidden>
- Date: Sun, 14 Nov 2010 10:25:40 -0600
On Nov 14, 2010, at 3:58 AM, Antonio Nunes wrote:
> I'm developing a daemon to run tasks as files get dropped into a folder. When a new file is detected, a new operation gets put onto the daemon's operation queue. The operation launches a task and waits for it to finish, then the operation exits.
>
> The issue I am seeing is that the operation queue appears to indiscriminately run operations as they are added to the queue, regardless of system resources, thus bringing the whole system to a crawl. I thought, from reading the documentation, that the queue would be intelligent about how many operations it would have running at the same time, depending on system resources such as number of cores and available memory.
The system can't predict the future. When an operation is pending in a queue, there's no way for the system to know if it's going to be CPU intensive, memory intensive, and/or I/O intensive.
Let's suppose the system primarily governs its thread pool based on available CPU time. Let's also suppose that your operations start with some blocking I/O but then switch to computation. When there are operations queued, but most of the already-running operations are blocked doing I/O, then the CPU may have idle capacity. So, the system starts another operation (or several). It will have effectively "overbooked" the CPU -- in short order, as I/O requests are satisfied, there will be more operations doing CPU-based computation then there is CPU capacity to handle them.
It's also the case that disk I/O degrades significantly with contention. So, even just having a bunch of I/O-intensive operations queued can burden the system, even while there's plenty of spare CPU and memory capacity.
There's discussion of a related issue, this time having to do with Grand Central Dispatch, at Mike Ash's blog:
http://www.mikeash.com/pyblog/friday-qa-2009-09-25-gcd-practicum.html
> Since this doesn't seem to be the case, I have to assume something is not quite right with my implementation.
Not necessarily.
However, one approach to working around the system's limitations is to separate your operations into pure I/O operations and pure CPU operations. (If one logical operation involves both, you can split it and use dependencies to have the two halves run in the appropriate order.) Schedule the I/O operations to a queue with a fairly small maximum concurrent operations limit. If you want to get really fancy, you might have separate I/O queues for each device (e.g. disk) to maximize parallelism. Schedule the CPU-intensive operations onto general, unlimited queues that the system can manage. These operations should never block, if you can avoid it.
By separating the types of work, you give the system another opportunity to manage the load. So, if one operation first does some I/O and then does computation, the system will be "fooled" by the CPU lull at the beginning into launching more operations and then hammered by the change to computation. When the types of work are split, the I/O operations may be launched because there's free CPU capacity, but at least when the computation kicks in the system gets another chance to decide whether there are free resources to handle it.
> Also, is there a way to find out the number of cores on a machine so that I can set that as a hard limit of number of operations on an NSOperationQueue?
-[NSProcessInfo activeProcessorCount]
> Here is an outline of how my code implements the daemon and the operations:
[snip]
It appears that your program is launching copies of itself to process individual files. I suspect you're "bringing the whole system to a crawl" by a fork bomb. Have you checked that your program is not infinitely-recursively launching subprocesses?
Also, why are you re-running your program in a subprocess, instead of just having your operation do the work directly? (I suspect you do it that way in case processing of a particular file causes a crash. You want to limit the crash to just the process handling that file.)
Have you considered letting launchd monitor the directories, launch your daemon, and deal with any crashes?
I'll also say that having NSOperations launch an NSTask and block waiting for it to exit is nearly a worst case scenario for NSOperationQueue. It has a thread pool with idle threads, so it figures it can pull a new operation off the queue to start it with minimal new resources. That operation not only monopolizes the thread, it does effectively nothing with it. Instead, it launches a whole separate process, which is a significant use of new resources.
At least you could avoid the waste of the worker thread by launching the subprocess from the main thread. Instead of blocking as you wait for it to exit, use the asynchronous notifications to trigger any followup work. Of course, this approach doesn't integrate with NSOperationQueue, but you're effectively undermining that, anyway.
Regards,
Ken
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden