Cocoa Programming for Mac OS X - Aaron Hillegass [130]
Figure 34.4. Show the Extended Detail pane
At this point, it’s pretty clear that quite a bit of time is being spent creating the thumbnails. Try double-clicking on the stack frame for thumbImageFromImage:. The detail pane will change to show the source code of that method, with highlighting to show how much time is spent on each line (Figure 34.5). Use the jump bar control above the detail view to return to the Call Stack.
Figure 34.5. Source Code Display
Another useful feature of Instruments is constraining the Inspection Range. You can set the range by using the toolbar buttons, but it’s much faster to hold down the Option key, click, and drag over the timeline. The information in the detail view will be limited to the selected range of time. This is helpful when focusing on a specific performance problem.
We’ve only scratched the surface of what’s possible with Instruments. As we’ve seen, it can be useful as a time profiler, but it also has a number of tools for dealing with memory issues, including tracking usage and detecting leaks and strong reference cycles in ARC. As you work to improve the performance of your applications, you will want to read Apple’s documentation for Instruments. WWDC videos can also be a great resource for learning to use Instruments.
Analysis
Now we know for sure where the problem is: generating thumbnails is time-consuming; even worse, we’re doing that work in applicationDidFinishLaunching:, which ties up the main thread while the directory tree is traversed and each thumbnail is created.
There are generally two categories of blocking problems like this: CPU-bound and I/O-bound. I/O-bound problems revolve around waiting for slower hardware to do its thing and return control to us. In a CPU-bound problem, the CPU is the bottleneck; decompressing dozens of JPEG images relies heavily on the CPU. As we saw in Chapter 28, I/O-bound problems can sometimes be solved using asynchronous I/O, but doing so in this case could be complicated.
In this case, we appear to have a mixture of both problems: It is disk-intensive to load many megabytes of data and CPU-intensive to decompress and draw scaled-down thumbnails. One hint that this is the case is that the CPU is not 100% utilized (if it were, the Time Profiler’s graph would be straight across the top). The simplest solution in cases like this is to put the work on a background thread.
We could do that by using performSelectorInBackground:withObject:, but that’s just one thread, so we wouldn’t be taking any advantage of a multicore machine. Creating multiple threads in this fashion would get complicated very quickly. We need something more sophisticated.
NSOperationQueue
Frequently, multithreading is used for processing chunks of information in the background. In such cases, Cocoa’s NSOperationQueue provides a much more mature framework for organizing the processing, compared to the rather informal performSelectorInBackground:withObject: and even creating threads manually using NSThread.
NSOperationQueue represents a collection of operations (encapsulated by NSOperation) and manages the execution of those operations on one or more threads. Every application has a main queue that represents the main thread; it is accessed by [NSOperationQueue mainQueue]. If the application needs additional queues, it can create and configure them simply by allocating and initializing a new NSOperationQueue.
By default, NSOperationQueue objects are configured to run several operations concurrently; the exact number is determined automatically by the system. You can override this configuration by calling setMaxConcurrentOperationCount:. A maximum concurrent operation count of 1 results in a serial queue. The main queue is always serial.
Multithreaded Scattered
Let’s modify Scattered to use NSOperationQueue and try to improve it.
Open ScatteredAppDelegate.h and add an instance