Wednesday, 7 May 2014

Launching Instruments [Faster, Faster]

Instruments is the front-end for a suite of analysis, profiling, and faultfinding tools, much as Xcode is the front-end for your editors, compilers, and debuggers. You can use the Instruments app by itself, but Xcode makes it soeasy to use directly from your project, I cant imagine why you’d want to.

For this chapter, start with the version of MyStuff after you added document support and fixed the image rotation problem (second exercise in Chapter 19). You’ll find this project in the Learn  iOS Development  Projects  Ch 19 MyStuff  E2 folder.

Instruments is normally launched when you profile your app. This is determined by a set of schemes defined in your project. When you run your app (using the Run button or menu command), the
Run scheme is used. When you profile your app, the Profile scheme is used. To see your projects schemes, choose Edit Scheme... from either the Scheme control in the toolbar or by choosing Product  Scheme  EditSchemes... from the menu. This will present the scheme editor, as shown in Figure 23-1.


Figure 23-1. Project scheme editor


Theres nothing here to change, I just wanted you to know where it was. The default configuration for the Profile scheme is to launch Instruments and prompt you (Ask  on  Launch) for the type of analysis to perform, which is perfect to get started. So what are you waiting for? Close the scheme editor and profile your app by choosing Product  Profile (Command+I) from the menu or from the Run buttons drop-down menu. Xcode will build your project and launch Instruments, as shown in Figure 23-2.


Figure 23-2. Instruments template picker

Each Instruments template describes a different kind of analysis. They’re organized into groups for
convenience. For your first foray into performance analysis, choose the iOS  CPU group and select the TimeProfiler. This is the most commonly used instrument for measuring code performance. It works by sampling
youcode as it executes, thousands of times a second. At each sample, it records the functions and methods
that are executing at that instant. By aggregating hundreds of thousands of these samples, it canpaint
a remarkably accurate picture of where your app is spending its time. These are the methods that you want tomake faster.

Click the Profile button and your app begins executing, under the watchful eye of the Time Profiler instrument, as shown in Figure 23-3. You want to find out whats taking so long when a new image is added, so immediatelystarted adding new items and selecting images for those items from your camera roll. Repeat this several times. You should notice a sizable spike in CPU activity as you tap on an image in the photo picker, creating a series of“humps” in the CPU usage graph.


Figure 23-3. Initial sample of MyStuff

After adding a few new images, press the home button to push the app into the background, wait a few seconds, and click the Record button in the Instruments toolbar. This stops recording and terminates the app.

Congratulations, you have a baseline! You’ve captured the code activity associated with the performance
problem you’re trying to solve. Now its time to mine this mountain of data for some answers.

Finding the Hot Spots

Begin by isolating just the performance information you’re interested in studying. Using the mouse in the Instruments timeline (at the top of the window), drag the sample cursor (hollow triangle with dotted line) just to the left ofone of those “humps” in the graph recorded just as you tapped an image in the photo library picker. To make this easier, drag the Change Track Scale control, shown at the middle-left of the window in Figure 23-3, to zoom in on the samples you’re interested in. Click on the left mask button in the Inspection Range control in the toolbar. Drag the cursor just to the right of the hump and click on the 
right mask button. Now all of the data you’re goingto work with in the lower panes will contain only samples from the highlighted range (lower pane in Figure 23-3), because this is the code you’re interested in profiling.

Now you want to find the hot spots in your code. This is optimization slang for the code thats eating up all of your CPU time. Less colorfully, its the regions of code that accumulated the most samples. With the Time Profilerinstrument selected in the trace document, locate the Invert Call Tree and 
Show Obj-C Only options in the Time Profiler sidebar. Check both of these, as shown in Figure 23-3.

The Show Obj-C Only option filters out all of the functions from the analysis. I recommend this option for Objective-C programmers, particularly to get started.

The Invert Call Tree option inverts the Call Tree you see to the right. When not checked, the Call Tree summarizes the calling hierarchy of your entire app. Each line in the table shows a method or function and how much timeyour app spent in it. Expand a line and you see the methods it invoked, and the breakdown of time spent in each sub-method. Expand one of those, and you get the idea.

The Call Tree is normally sorted by “heaviest” method. That is, the first line of each group will be the method that used the most CPU time. To dig down into the pile and find the heaviest code path in the tree, keep expandingthe first line of each group.

With the Invert Call Tree option checked, the Call Tree is turned insidout. Now, the method listed athe top is the heaviest leaf method in your app. Expand it, anit lists what methods called it—instead of what methods itcalled. In Figure 23-3, you see that 32.3% of your apps response time was spent in the -drawInRect:blendMode:alpha: method. 
Working backwards, by expanding the lines, and you find that its the -imagePickerController:
didFinishPickingMediaWithInfo: method thats using it (to resize and crop the chosen image).

Looking a little further down the list, the next heaviest method is the -setImage:existingKey: method. This is the method you added to store the new image in the document. When the user taps on an image in the picker, 23.7% ofthe time is spent storing it in the new document. If you expand its
caller you see that it, too, is being called from the -imagePickerController:didFinishPickingMediaW ithInfo: method.

This corroborates your suspicion that the image conversion and document storage code you added in Chapter 19 are slowing down the interface when the user picks an image. Lets dig into the
-imagePickerController:didFinishPickingMediaWithInfo: method and find out whats going on. Turn the Invert Call Tree option off again, and begin expanding the heaviest methods in the call tree until you uncover the -imagePickerController:didFinishPickingMediaWithInfo: method, as shown in Figure 23-4.



Figure 23-4. Details of -imagePickerController:didFinishPickingMediaWithInfo: execution time

In this set of samples, it shows that the image picker delegate method spent close to a second (860 milliseconds) responding to the tap. Expanding that line, you see the amount of time spent in each message the pickermethod sent. While this is accurate information, its sometimes a little hard to translate into what your code is doing. Instruments will help you here too.

Double click the -imagePickerController:didFinishPickingMediaWithInfo: method line and Time Profiler will switch to its source view, as shown in Figure 23-5.


Figure 23-5. Time Profiler source view

Instruments overlays the time spent in each line of your source code, clearly identifying the hot spots in your code. This view is particularly good at identifying loops that are taking a long time to execute.

What have you learned from Figure 23-5? You’ve learned that when the user tapped on an image in the photo picker, 37.3% of the time was spent cropping image, 26.9% storing it in the document, and 0.3% dismissing thephoto picker view controller. In a big surprise, 34.8% of the time was spent notifying any observers that the data model changed. Huh?


The Hubris of Experience

Yes, it caught me off guard too. When I was originally planning the projects for these chapters, I knew that the image compression and document storage code in Chapter 19 would add a lot of overhead to the MyStuff app. I’veworked with the LZ77 compression algorithm used by the PNG format, and
I know how CPU intensive it can be. I planned to start this chapter by showing you how much time the
image conversion and compression routines were taking up when the user chose an image, and what to do about it. But when I actually ran Instruments on MyStuff, what did I find? I find that almost a third of the 
delay is tangled up in the data model notification. Something that I guessed wouldnt have even showed up on the radar.

And this is why you cant assume you know where your performance problems are. Even with years of programming experience, you’re going to guess wrong. I did. You must start by measuring your real-world performance. Ifyou dont, you’ll be tilting at windmills.

So what is going on? Digging into the -postDidChangeNotification method, shown in Figure 23-6,
it turns out that the MyWhatsit change notification was observed by the view controller, and that triggered a redraw of the table view(-reloadRowsAtIndexPaths:withRowAnimation:). This, apparently, is a much more expensive operation than I thought.


Figure 23-6. Time sample details of -postDidChangeNotification



Fine, I guessed wrong. Can we get back to improving MyStuff now?

Picking the Low Hanging Fruit

With all of this knowledge, you now need to formulate an optimization plan. You’ve identified three hot spots in your photo picker handling code:

n     Data model notification

n     Image conversion

n     Image compression and document storage

The trick is to pick the “low hanging fruit.” Find the code that is hurting performance the most and is the easiest to improve.

Graphics operations are data and CPU intensive, by their very nature, and the iOS graphics library
is already heavily optimized. Its unlikely you’re going to get much improvement by rewriting the code that crops the image.

That leaves the data model notification and document storage as candidates for improvement. And in a nice surprise, the heaviest is also the easiest to solve.

Deferring Notifications

In iOS, notifications are delivered immediately. When you post a notification, all of the observers are sent their notification messages, and control returns to your method only after they’re all done. In this instance, the act ofnotifying everyone that the data model object changed triggered a series of expensive and time-consuming operations.

You cant avoid this work—the data model has to send its notifications, and the observers have to
be notified—but you can procrastinate. Its not critical to the code that picks a new image that these notifications are delivered before that code is finished. You can take advantage of this to defer the notification. The notificationwill still happen, just not at the moment the user taps an image. This will, in turn, improve the response time to the tap event.

The notification center has an oft-neglected relative called the notification queue (NSNotificationQueue). A notification queue posts notifications to a notification center on your behalf, but it provides two essential services. It doesntpost them immediately, so any code triggered by those notifications wont execute until later. Secondly, it will combine duplicate notifications and only post one; a feature called coalescing. Some notifications, like data modelchanges, can occur many times, but they all mean the same thing. Rather than repeatedly send the same message to all observers, the notification queue combines them into a single notification, which is far more efficient.

Open your MyStuff project. Select the MyWhatsit.m implementation file in the project navigator and locate the -postDidChangeNotification method. Rewrite the method like this (new code in bold):

-  (void)postDidChangeNotification
{
NSNotification *noti;
noti = [NSNotification notificationWithName:kWhatsitDidChangeNotification object:self];
[[NSNotificationQueue  defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle];
}
To use a notification queue, you must create a notification object. (When you use the notification centers -postNotification... messages, the NSNotification object is created for you.) Once you create the notification, push it ontothe queue.

Each notification queue is attached to a notification center. iOS, conveniently, creates a notification queue attached to the default notification center for you. Anything you add to the default queue will (eventually) be posted tothe default notification center.

When your modified MyWhatsit object receives a -postDidChangeNotification message, it doesnt notify its observers immediately.
This method has become an asynchronous method. It merely queues the notification to bedelivered in the future.

So when does it get delivered? You have are a few choices, controlled by the postingStyle
parameter. The two most useful are NSPostWhenIdle and NSPostASAP.

n     NSPostASAP will post the notification as soon as the current code finishes executing and control returns to the event loop. Use this to post notifications that must be sent before the next event isdispatched.

n     NSPostWhenIdle saves notifications until the event loop is idle. It waits until all pending events (touch events, timers, deferred messages, user interface updates) have been dispatched. Before the event loopgoes to sleep again, any queued notifications are sent. Use NSPostWhenIdle when you want notifications to be handled immediately after you’ve responded to all user and timer events.

Now the work of updating the data model, refreshing the table view, and notifying the document of changes is still handled, but its handled after you’ve responded to the tap event, dismissed the view controller, and updatedeverything on the screen.

Once More into The Breach

Close and save your Instruments trace document, as shown in Figure 23-7.
Give it a descriptive name, like Baseline Profile. Close the profile window.


Figure 23-7. Saving your baseline trace document


Return to Xcode and profile your app again. Perform the same steps you did before: choose the Time
Profiler template, add images to your app, stop it, narrow the inspector range to the code that picks a new image, andreview the call tree, as shown in Figure 23-8.


Figure 23-8. Profiling revised code

This time, it only took half a second (514 milliseconds) to execute the code in the -imagePickerController: didFinishPickingMediaWithInfo: method. Thats an improvement of over 67%. Open the baseline trace document you saved andcompare two, similar, execution sections. In the modified code,
the-postDidChangeNotification method doesnt even show up in the sample, it happens so fast!

If you dig around the call tree, you’ll eventually find where the data model notification is posted, and all of the busy work associated with it still gets done. But its not happening when the user taps the interface, so they dontsee it.

Your app should feel much more responsive now. The key to creating responsive apps is to keep your event loop running and ready to react immediately to the user or other events. That often means breaking up
the work so itdoesnt happen all at once. Notification queues are a great way to defer tasks that you dont want interfering with your interface, and can be safely performed later.

This is great progress, but it still hasnt gotten the response time below ½ second. The next “heavy” on the
list is the code that adds the image to the document. You’ll learn a different way of deferring that work in the nextchapter.

Save your second trace document and close it. Now you’re ready to look at a completely different aspect
of your apps performance.

Precious Memory

The amount of RAM available to mobile apps is minuscule when compared to most computing
environments. Yet, users expect mobile apps to accomplish many of the same tasks: surf the web, play videos, read books,and edit documents. An iOS app is under tremendous pressure to conserve, and make the best use of, its memory. Xcode has several tools to help you analyze and improve your apps memory use.
Quantifying memory use isnt as conclusive as measuring code performance. Poor use of memory degrades your apps, other apps, and the iOS device in general, in odd and subtle ways. Generally, you want your app to useas little memory as possible. But that can be counter to making your app faster, because caching data and objects is one of the ways of improving its performance.

In this section you’re going to work through the bare minimum of responsible memory use by stress testing your app and responding to iOSs low memory warnings. When an app starts to use too much memory, iOS firstgives it a chance to release memory its not relying on. It does this by sending low memory warning notifications. If the app responds, it (and other apps) can all continue to run. If it doesnt, or cant recover enough memory,iOS will begin terminating apps, in order to keep the active app running.

If an app continues to ignore these warnings, it runs the risk of running out of memory. This usually has disastrous results. The app will crash, and the user will be left staring at the springboard. This situation should be avoidedat all costs.

In this section you’re going to use Instruments to trace the memory usage of your app and find its breaking point. You’ll then modify your code and use Instruments again to verify that it behaves correctly. But first, you haveto push it to its limit.

Breaking MyStuff

MyStuff is an unbounded app. You put no limits on the number of items the user can add. Now you might expect that number to be reasonable, and MyStuffs memory needs to be nominally modest. And most of the time, you’dbe right. But if I have 20 model train cars, you know theres somebody with 300. What happens to MyStuff when the user wants to track 100, or 500, or 2,000 items? Is that crazy? Absolutely. Is it OK if MyStuff crashes after adding the 500th  item? No, it is not.

When you develop any app, you need to stress test it. How you do that will depend on the nature of the app. For MyStuff, its Achilles’ heel is the memory used by all of those item image objects. If the user keeps adding itemsand images, its going to run out.

You’ve already written MyStuff to keep its image objects in memory, lazily loaded from the documents file wrapper objects. You also have to establish a performance criterion: MyStuff should use its memory efficiently and notcrash if the user enters 1,000 items. Thats a reasonable goal.
The next step is to test it.

I dont know what you’re doing this weekend, but I dont want to spend it entering 1,000 items (with pictures) into MyStuff. I suggest you do what any respectable programmer would do; you cheat. Add some code to MyStuff togenerate hundreds of test items.

The first step is to make MyWhatsit copyable. Select the MyWhatsit.h interface file and adopt the NSCopying protocol (new code in bold):


@interface MyWhatsit  : NSObject  <NSCoding,NSCopying>



Switch to the MyWhatsit.m implementation file and add the necessary -copyWithZone: method (you learned how to do this in Chapter 20):

-  (id)copyWithZone:(NSZone *)zone
{
MyWhatsit  *copy  = [[[self  class] alloc] init]; if (copy!=nil)
{
copy->_name = _name;
copy->_location = _location; copy->image  = image;
copy->imageKey = imageKey;
}
return  copy;
}

Select the MSThingsDocument.m file, find the -loadFromContents:ofType:error: method, and locate the code that unarchives the things array. Immediately after that, add this test code (in bold):

if (thingsData!=nil)
{
things = [NSKeyedUnarchiver   unarchiveObjectWithData:thingsData];
#if 1  // STRESS  TESTING:  generate a  thousand  test  items if (things.count>10)
{
NSUInteger cloneIndex = 0; while  (things.count<1000)
[things  addObject:[things[cloneIndex++] copy]];


#endif

}

[things makeObjectsPerformSelector:@selector(setDocument:) withObject:self];

}
Run the modified MyStuff app. Add more than 10 items to the list, with images. Press the home button, to push the app to the background, and let it save its document. Run it again. This time, your list will have 1,000 items.Now its time to see how well MyStuff handles that!

Measuring Memory

Memory and CPU usage are such important performance metrics for apps that Xcode includes a set of “mini” instruments right in its debug navigator. Run your MyStuff app from within Xcode. Switch to the debug navigator (if itdoesnt appear automatically), as shown in Figure 23-9.


Figure 23-9. Xcode CPU and memory monitors

The stress-test code you added will generate a 1,000 test items. Start scrolling through the list. As each table row reveals a new MyWhatsit item, it requests the items image. This, in turn, loads a new UIImage object from thedocument and saves it in the image property of the object.

As you scroll, you’ll notice the memory use of the app begins to climb, as shown in Figure 23-9. Keep scrolling and the app will eventually run out of memory. You’ll be rewarded with a fatal app crash, accompanied by themessage shown in Figure 23-10.



Figure 23-10. Memory pressure failure

Xcode explains that your app Terminated due to Memory Pressure,” which is a polite way of saying it used up all of its available memory and died an ugly death. This is horrible. You definitely dont want this happening to your users.

The CPU and Memory monitors in Xcode are great for spot-checking your apps use of these preciouresources, but when theres a big problem you’ll usually need more detail. Turn again to Instruments.

No comments:

Post a Comment