Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: tech-talks-2011-extra-6
$eventId
ID of event: tech-talks
$eventContentId
ID of session without event part: 2011-extra-6
$eventShortId
Shortened ID of event: tech-talks
$year
Year of session: 2011
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2011] [Tech Talk China] Optimiz...

China Tech Talks #6

Optimizing App Performance with Instruments

2011 • 43:47

Great iOS apps delight users by performing flawlessly. From using memory conservatively, working with graphics efficiently, writing fast code, to avoiding blocking networking calls, learn tips and techniques to take your app to the next level.

Speaker: Michael Jurewitz

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Hi, I'm Michael Jurewitz, Apple's Developer Tools and Performance Evangelist. And today, we're here to talk about performance. Now, great apps have terrific performance. They delight users by being fast, being responsive to user input, and they use system resources efficiently. So today, we're going to look at some tips and best practices for helping your app perform great. So let's get started. So as I mentioned, we're here today to talk about performance. Now, performance is perhaps the most important thing for you to pay attention to in your application. So you might be wondering, what is performance? Well, performance is about being fast.

It's about responding to your users quickly. It's also about being responsive, making sure that as they interact with the screen, your application is responding to their touch. And it's also about being efficient, making sure that your app is responsive to your user input. And it's also about being efficient, making sure that you're using system resources wisely, so that as you go to use something like memory, you're just using the memory you need, and you're not spiking all over place and causing the system to thrash.

But beyond even that, it's about having a great impression with your users, because they notice these things. So you want to make sure that you put your best foot forward, so that you don't end up with reviews like this. People saying that your app is slow and buggy, or that... It crashes all the time, or even worse, one star.

So today, we're going to focus on teaching you what you need to know to have a great performing application. We're going to take a look at how to test and measure performance, show you how to launch faster, and also show you how you can reduce your memory footprint. So first, let's take a look at measuring performance.

Now, the first question that probably comes to mind is, what are the things that you should be measuring? Well, in short, you need to be looking at everything. You need to be looking at processing time, how much time you're actually spending on the processor trying to get your work done.

You want to look at your memory usage to make sure that you're not using too much, that you aren't leaking memory. You want to look at your graphics performance and the correctness for how you're actually doing your drawing. And there are some important points to pay attention to here. You want to be looking at disk I/O, so you know how you're interacting with the disk. And you want to look at battery life, because your user wants to be able to continue to use their iPhone or iPad throughout the entire day.

But to look at all this, you need some help. And we've got an app to help you do that called Instruments. Now, Instruments is your one-stop shop for all your performance needs. And it gives you a holistic view of everything that's going on in your application, from where you're spending that processing time to how much memory you're using and your graphics performance. and battery life performance.

Now Instruments comes with a ton of templates by default. So let's take a look at a few of those. First, you've got the Time Profiler template. This provides you low-level statistical sampling of your application so you know exactly where you're spending time and what algorithms you need to tune.

Next, you have the allocations template. The allocations template lets you look at the memory usage in your application to understand how many objects you're allocating, when you allocated them, and look at detailed information, like even their retain counts. The core animation instrument lets you look at that valuable graphics correctness that I talked to you about before. It's a really, really powerful instrument. Finally, new in Xcode 4, you have the OpenGL ES Analyzer instrument, which gives you an in-depth look at how you're using OpenGL and can really help you tune that performance. It's really vital, especially if you're a game developer.

You also have the system activity template so you can see how you're interacting with the disk and exactly what's going on on the device. And finally, the power and battery life template lets you look at the power consumption of your application and overall see how it was draining power from the device so you can better fine tune things to reduce battery consumption. Now, when you start looking at what you need to do in measuring, the most important thing is don't guess.

Now, I'm going to say that again. Don't guess. You've got a powerful set of instruments at your fingertips to figure out exactly what you're doing. So even though you architected the code and you've got a great idea for what you think needs to be tuned, measure first. It's absolutely important that you measure. And most importantly, you want to focus on simple, repeatable, real-world actions.

Does your app have a table view? Does the user scrolls through? Tests scrolling through that? When they tap on an entry, do you load up additional data or go out to the network? Measure that experience specifically. Focus on these small, repeatable actions that you can fine tune to make sure that they perform great.

And when you make a change, make sure that you go ahead and you retest, that you verify that you actually have made an improvement. Because the last thing you want to do is make a change that causes things to perform worse. And you want to keep iterating until it just feels right, until it feels great for you to use in your hand.

Now when you're looking at something like Instruments, there's a few things that you want to look for in particular in your application. With something like the Allocations Instrument, you want to make sure that you're not using tons of memory, and that it's not growing monotonically over time. Your app should ebb and flow as it uses memory, and it shouldn't just be marching upward constantly. Using something like the leaks instrument, you should absolutely not have leaks in your app. You need to make sure that these get eliminated. And we'll talk about how to do that a little bit later.

And when you're using something like the Time Profiler instrument, you want to make sure that you're not pegging the CPU and using all of the available resources as much as possible. You want to make sure that you're tuning your algorithms to get their work done and then stop, so that you can do things like make sure that battery life lasts longer. Now when you're measuring performance, you want to make sure that you choose the right tuning environment.

And it's important to remember that the simulator is a perfectly valid place to do a lot of performance testing. The simulator is fast, it's got rapid turnaround, and in this case of some instruments, it even has additional options that you can use to actually improve your analysis. So in the simulator, for example, you can use things like Activity Monitor or Leaks or Allocations. Generally speaking, the memory performance doesn't change between the simulator and the device. And so this is a perfect place to test that kind of correctness. But at the end of the day, the device is where you need to be doing your final testing.

And it's also where you need to be doing the kind of testing like Time Profiler, Core Animation, or the OpenGL ES Analyzer. Because those instruments and that performance really depends on the underlying device that you're running on. You need to know how the processor is going to behave, how the graphics processor is going to behave.

And so you want to make sure that you're measuring where it matters. So the simulator is great for rapid turnaround. You've got additional memory debugging features. But generally speaking, it's unrealistic for things like timing. Because of course, the simulator is running on your laptop or desktop, which has got a really powerful Intel processor in it. And the device needs to be the final arbiter. So use it for all speed-related testing.

And make sure that any memory fixes that you make in the simulator also bear out when you test on the simulator. All right, so let's talk about launching quickly. So now launching quickly is so important because it's the user's first impression of your application. From the moment they touch your app on the home screen and it begins to launch, they're immediately forming opinions about your application. This is your vital opportunity to communicate things about speed and the quality of your application. And you want to make sure that you're putting that best foot forward.

So the common scenarios that we're going to take a look at in this section are both the non-multitasking launch of your app. So this is just when the user is launching your app from the first time. And we're also going to talk a little bit about the multitasking resume.

This is when the user double taps the home button, presses your app, and you get reanimated from having been in the background. So when it comes to launching quickly, timing is everything. The OS is watching your app constantly. And slow performance will cause iOS to quit your application. Now this seems kind of drastic, so why do we do something like this? Well, it's very important that if someone's using their iPhone or their iPad or their iPod Touch that the system remain responsive.

We want to make sure that they can continue to use the device for what it's intended for. And if your application hangs and stops being responsive, then for all we know, the application may never recover. And so we need to make sure that the user can continue to use that device.

So the timings we give you are rather generous, but you need to be aware of them so that you don't run up against them. So for example, for launch time, you have 20 seconds from the time your app launches to the time you've done executing application did finish launching with options and loaded your first view to be able to start turning the run loop and accepting user input.

If you take longer than 20 seconds, your application is going to stop running. If you take longer than 20 seconds, your application is going to get killed. When you're resuming from the background, you've got 10 seconds to come back. When you're being suspended into the background, you have 10 seconds to do any cleanup that you want.

And finally, of course, if you're just being quit by the user, you've got six seconds to clean everything up. And we also have Task Completion API as part of the new multitasking API in iOS 4, so that if you have a long-running task, like an upload that you need to finish, you can get up to 10 minutes to run in the background to be able to finish that kind of activity. Now, when you're measuring launch, you need to test with realistic data sets. This is absolutely important.

Now, when all of us are doing development, it's really common for us to just have a blank set of data that we're working with because you've just built the app and pushed it off to the device. But if you've got the kind of app that expects to be collecting tens, hundreds, thousands, tens of thousands of pieces of data, and you've got algorithms that load all this stuff on startup, well, then you're going to go from having launch quickly to possibly having your app being killed on startup for your higher-end users. So make sure you're testing for those real-world scenarios. And you can do that by using the Time Profiler instrument.

[Transcript missing]

So, let's take a look at a demo for launching quickly with the Time Profiler instrument so I can show you how to find this kind of activity. Okay, so let's take a look at helping your app launch even faster using instruments. So first, we're going to open up our project here in Xcode. And I'm going to go to Build and Run so we can take a look at what it looks like.

We're building our application, it's being moved out to the device, and we can see it's taking a long time to launch. We're waiting, we're waiting. Still waiting. And finally the content has come in. And as you see as we scroll through, we've just got a ton of content.

If you look at the scroller on the right side, you can get an idea for just how much we have. So clearly this took a long time, and we'd really like this to be faster. So let's look at how we can optimize this performance. So we'll go back to Xcode.

And now, we're going to launch Instruments. When we launch Instruments, we're greeted with this series of templates for us to choose from. So we can take a look at the memory usage of our application, use the time profile of our instrument to understand where we're spending time, and even use things like UI automation or look at the energy diagnostics for our application. In this case, we're concerned about speed. So let's take a look at the Time Profiler application.

Now I'm going to go ahead and choose Target and select our Launch Performance application and hit Record.

[Transcript missing]

Now if we go and we look at the left side of the screen here, there are a bunch of different options for filtering the data that you're looking at.

By default, the time profiler comes with the call tree inverted. When I'm looking at information like this though, I like to un-invert the call tree and start at main and work my way down. Now a really useful option if you're just using Objective-C in your application is to check this Show Objective-C Only. You can also check flatten recursion in case you have any recursive algorithms and you just want to flatten them into a single frame.

Now over here in the table view we've got the actual data from the run. And there's a few different things you can look at in this data as well. So if I control click on this menu, you see there's a lot of different stuff we can pay attention to. In particular, I want to look at self percentage.

Now we've got running time, which shows a percentage, and self percentage. So what's the difference here? Well, in the case of running time, running time is going to show you the total percentage of time that was spent in this method and everything else that it called through to. In the case of self percentage, what you're looking at was the time that was spent just in that method itself, so not the things that it called through to, like frameworks or other methods.

Now this is particularly important because you're going to be able to use this information to know, do you need to optimize how you're using the frameworks and perhaps use them better or use different ones to get better speed? Or do you need to tune that method itself to help it go faster? Now from here, the important part is just to start stepping down the call tree. And you kind of want to be a detective and just follow this large percentage. So we keep going down. We see we're still at 96.3%. We're in UI Application, Handle Event with New Event.

We keep going, we've got more framework code that we're working with. We're continuing to follow this percentage. And finally, we've hit the Launch Performance App Delegate, Application did finish launching with options. Now, if you ever do a trace and you see this amount of time spent in Application did finish launching with options, again, this should be a big red flag. You absolutely don't want to be spending this amount of time in your own code in that method.

Your whole goal at this stage should be to kick off your application, to get it running, to do bookkeeping and take notes for yourself about different state that you need to set up, but you shouldn't be blocking while you do all this state loading at this point in time. So let's see exactly where we're spending time here.

If I turn this down, we see we've got our Sans View Controller calling a method called parse. If I didn't know this code, this would already be another red flag saying, uh-oh, am I parsing and loading data on the main thread as part of launching my app? Sure enough, we've got this RSS parser here, and we're telling it to start, and it looks like it's just parsing through XML. Worse yet, we've got this download and parse method, so it sounds like we may actually even be doing some network activity as part of that.

And sure enough, we get into framework code with NSXML parser, where we're actually parsing this XML from this feed that's come in. Now, if you're looking at your code and trying to figure out exactly where you're doing this, it's actually really easy in Instruments to see that. We can just double-click on the method itself, and we actually jump to the source, so you can see exactly where you are doing this work.

And if you want to go to Xcode and be able to make a fix, perhaps change how you're doing this, you just press the little Xcode button, and now you're back in your project. And so at this point, you want to be paying attention to this method, or even just how you've architected this loading, and try to make changes so that you're not loading this data on the main thread, and really that you're just avoiding this parsing in general.

Alright, so remember, the system will terminate slow applications. You want to make sure that you collect data with Time Profiler. You want to make sure that you defer and otherwise just try to do less work. Avoid blocking the main thread at all costs. And finally, optimize, optimize, optimize. Be using Time Profiler to figure out exactly what you need to improve in your application.

Okay, so that's launching quickly. Now let's take a look at reducing memory footprint. Now, there's a lot of things going on in the device at one time. For instance, you've got 12 megabytes of memory that's going to the graphics processor. You've got a good 36 megabytes that are used by the kernel, it's wired memory. Approximately another 10 or so that are used by the different daemons on the phone. You've got the springboard, the actual home screen takes up another 8.

And then you have the processes that actually make iPhone and iPod touch what it is. You've got the phone process, the mail process, iPod, Safari, all of those have a memory impact. So it's important to understand that there are other things happening on the device at the same time that your application is executing. Now, iOS is a powerful operating system, but it's not a desktop OS.

It's a very limited memory to work with. At the end of the day, this is an embedded device that you're working with. There's virtual memory, but there's no swap file. So while you're not sharing memory with different processes, all of the memory that you're using has to stay physically resident in the RAM on the device.

Now to help you work in these situations, we also have low memory notifications that are sent to every process on the device to make sure that you can work together to try to free up resources as they're needed. So as you can see in the chart, there is a steady upward march in terms of the amount of memory that's available, but the system is going to be using some of this memory for itself as well. So you need to make sure that you're architecting your apps to use as little as possible.

And again, iOS has no swap file. All of that memory has to stay resident. And under sufficient amounts of memory pressure, the OS will actually begin terminating applications. Let's take a look at how that happens. So we have this system of low memory warnings. And as we look at memory increasing over time, there's a few things that happen.

[Transcript missing]

Finally, the last category is abandoned memory. Now abandoned memory is sort of a special kind of leak. This is memory that is accessible, but just in the own usage of your application, you never actually end up using it again. So the classic example might be a cache that you're keeping around, that you only are putting objects into, and you're not actually referencing it for the information that you're caching.

And so you end up just sort of slowly accumulating this memory over time, and again, just like with a leak, you're slowly suffocating yourself. And so you can use the allocations instrument with this brand new heap shot feature to be able to use your application and try to spot this kind of heap growth, so that you can go ahead and reduce that and make sure that you're not using too much memory. Now as you're going along, you want to make sure that you don't ignore low memory warnings.

These are really, really key to making sure that your app and the rest of the system continues to run great. So what should you do if you get a memory warning? Well, first of all, you want to make sure that you release any objects that can be easily reconstructed.

You want to release any cached objects, things that you can read off disk. Again, or that you really don't need, you were just keeping around in your caches. And you want to make sure that you unload cached resource files. So say an image that you can just go and read off disk again easily, especially if that image is not currently on screen. So when you receive these low memory warnings, you want to make sure that you take action. But most importantly, don't ask the user to do anything. There's nothing they can do. This is something that you as a developer, need to deal with in your application.

It's not like they can use your application more conservatively or somehow change what they're trying to do. Now, when it comes to responding to low memory warnings, there's a bunch of different places where you can do it. First of all, UIViewController subclasses can implement viewDidUnload. And as we'll see in a minute, this is actually very necessary.

In your app delegate, you can implement applicationDidReceiveMemoryWarning so that you can unload any resources that you need. And if you have any other objects throughout your application that want to know when a memory warning has come in, so they can do things like clean up caches, then they can register for UIApplicationDidReceiveMemoryWarningNot ification and trigger that behavior that way. Now, UIViewControllers will try to unload any off-screen views when a memory warning comes in. And they do this through calling viewDidUnload.

But they need to know that the memory warning is not coming in. So they need some help releasing views that you've retained in instance variables. So let's take a look at an example of what's going on. So let's say we've got our application here that's got this ComposeViewController currently on the navigation stack.

We tap on the photo, and now we've got this PhotoViewController on the stack as we're looking at this photo. And at this point, a low memory notification comes in. So what's going to happen is, is that the ComposeViewController is going to notice that the view is not coming in. So it's going to notice that it's off-screen, and it's going to release its reference to the UIView that it has.

But if you've got IBoutlets pointing to other views in that hierarchy, those are going to keep those views alive. So typically in your header, you might have something that looks like this. You've got a property declaration. You'll notice that property declaration is declared retain, which is how you declare IBoutlets.

And all of these are essentially going to have those views then retained, and they're not going to go away. So what you want to make sure that you implement is viewDidUnload, so that you're going through, you're setting each of these outlets to nil, and then calling super viewDidUnload.

And this is going to help that ComposeViewController to be able to get rid of that off-screen view hierarchy. So that now, it releases its view, viewDidUnload gets called, and you release your outlets. And then you can see that the view did not go away. and that memory goes away.

Now the best thing about memory warnings is that you can actually simulate them yourself in the simulator. So if you go to the hardware menu you will notice there is an item called simulate memory warning. This is a great way for you to verify that the classes that you have in your application are responding just the way you think they should to memory warnings. Now when it comes to multitasking again the key is to make sure that you are surviving in the background.

And it is also important to know that low memory notifications are not sent to backgrounded apps. Once your app is in the background and it has kind of been hibernated that is it. The memory that you have already managed to get rid of as you were going into the background is your only opportunity. And so you need to make sure that you release as many reconstructable resources as possible.

So when your app delegates application did enter background gets called or after you receive the UI application did enter background notification. Use that short period of time that you have to reduce your memory usage as much as possible. Apps that use less memory in the background have a lower chance of being killed by the system.

Okay, so now let's take a look at memory footprint and the allocations instrument to help you figure out how to improve the memory usage in your application. Okay, so let's see how we can get to the bottom of tough memory problems that you might run into when you're in development. So first, let's open up Xcode and take a look at our project.

In this case, it's a project called Breadcrumbs. I'm going to go ahead and click Build and Run, and let's take a look at what this looks like. So we've built the app, it's being pushed out to the device, And as we can see, it's a pretty simple application. We can go scrolling through it. Notice we've got a bunch of table view data here. Pretty straightforward.

So let's go back to Xcode now and see how we can use Instruments to look at the memory being used in this application. So we're going to bring up instruments and we're going to start off with the leaks template. Now as you recall, leaks are something that you absolutely need to make sure that your app is not doing. So you want to make sure that you use this instrument to get rid of anything you see that involves leaked memory. So we're going to go to Choose Target and select our breadcrumbs application.

And now we're going to go ahead and hit record. And on the device, we're now recording all of the allocation information from the device. As we scroll through the table view, we see we can see all these allocations happening. And what Leaks is doing is counting down to evaluating our process and looking for leaks.

We can see it's analyzed it, and sure enough, we found a bunch. We actually see we've found 50 different leaks just through scrolling through the table view. So obviously, this is a big issue because we've just scrolled, we've already leaked well over 2 kilobytes of data. If somebody's going to be using your app for a long period of time, that can really add up. And all of that is going to add up against your app and slowly suffocate you in the amount of memory that you have to work with. So let's dig into this and see if we can figure out where these leaks are coming from.

Now most importantly, you'll notice the leaks instrument is actually two instruments. It's leaks plus its allocations, so you can see all the different objects that are actually allocated. Now the allocations instrument has got a couple interesting switches that you'll want to know about. So the first is record reference counts.

Now what this is going to do is pay attention to every single object in your application and look at its reference count as it gets sent different messages like retain, release, malloc, free, and it's going to record all of this data for you. Now another one you should know about is called identify C++ objects. Now this can be important if you're an application that uses C++ because this is going to allow the allocations instrument to gain a whole lot more information about your objects in your application.

So let's go back to leaks and actually take a look at what was leaked. You'll notice it looks like it was a bunch of NSCF strings, which probably means it was just you using a bunch of NSStrings that ended up getting leaked. If we press this turndown, we can see all of the 50 different objects that were actually leaked. And in this case, we can see the responsible library as well as the actual responsible call that led to that leak.

If we bring in the extended detail view, we can also see where the allocation actually happened. We get to see a back trace for the allocation. So let's just pick one of these objects and see if we can get a little bit more information. So we'll click this little arrow here, and this will actually jump into detailed history for this particular pointer. We can see the point in time when the object was actually created, and we see it's got a reference count of one.

We see that it got retained with a reference count of two, and then it got released, and that went back down to a reference count of one. Now at this point, apparently the leak happened. We lost a reference to this object, but it's still around, it still has a retained count. So let's see if we can figure out why that is. Now we notice from this first entry where this object was actually created, that this happened in our root view controller. So if we double click, we can actually see the code where the allocation took place.

We see that sure enough it's an NSString called SubtitleText. And we just want to kind of scan through here and see how we're using it. We notice that we're using it as part of another call here for creating this breadcrumb cell. It all looks pretty ordinary. And later we do in fact release it. So it looks like we're pretty balanced with our usage of allocating and releasing it in this method. So there must be something else going on here.

So let's go back to the pointer history. Now we notice here at some point this object gets retained again. And interestingly, our table view method is still on the back trace, but now we've got this setter, this set location string. So let's look at what's going on there.

Looks like when we call setLocationString, we pass in a string. It makes a copy of what gets passed in and just assigns that to our instance variable. Now, the only problem here is that we're not actually doing any bookkeeping to make sure that the old value that was in that instance variable is getting cleaned up. So this looks pretty sure like this is probably our leak. So let's click the Xcode button so we can jump to our editor.

And now we can go ahead and select this text. Let's paste in a new version here. And in this case, what we're doing is we're checking to see if the instance variable and the input are different objects. If they are, we release the old value, we make a copy of the new value, and we update the instance variable. Michael Jurewitz So now let's hit Build and Run to get this pushed out to our device. We're building the app now. There's a bunch of assets, so it takes a few moments.

Our app is running. We're actually going to stop it for a moment just so we can go back and verify the leaks have gone away. So we hit record in instruments. And now we're taking a bunch of measurements. And if we go back to the device and start scrolling, we see that we've got a bunch of table view cells. And if we look back at Instruments, it's about to analyze our process.

And sure enough, the leaks are gone. So this is how you can use the leaks instrument to get rid of those pesky leaks in your app and reduce your memory usage overall. So today, you learned how to use instruments to track down hotspots in your code, identify memory leaks, and use system resources efficiently. I hope you'll be able to use the information here to make your apps perform even better. I look forward to seeing what you have in store.