显示标签为“Instruments”的博文。显示所有博文
显示标签为“Instruments”的博文。显示所有博文

2011年11月16日星期三

iOS 5 Tech Talk: Michael Jurewitz on Performance Measurement

iOS 5 Tech Talk: Michael Jurewitz on Performance Measurement:
The last Tech Talk session I am going to write about here is Michael Jurewitz’s talk titled Your iOS App Performance Hitlist:
The best iOS apps are not only beautiful and well designed, they also launch quickly, present a highly responsive interface, and use memory efficiently. Master the techniques to diagnose and fix common performance problems before your customers see them. Learn the performance hit-list you should apply to every one of your apps before it goes out the door.
In this session, Michael gave us a hands-on introduction on how to identify performance problems with Instruments based on three common problem areas. Overall, I liked this talk the best. I had seen sessions on Instruments before but sometimes you have to see things demo’ed two or three times until the learning kicks in. And I definitely feel that I learned a few new tricks here.


General Tips



The number one technique for measuring performance is to actually measure it. Don't guess!
Guesswork is almost never the right approach to performance optimization. It’s hard to optimize problems that you haven’t measured so the latter must always be your first priority. Apple provides Instruments to quantify different aspects of your app.


Another important factor is to test your app under realistic conditions. If you just test your app with a fresh developer build that contains little or no test data, it is very likely that you won’t catch many serious performance and memory problems that will later plague your users. You should always make sure to use realistically-sized data sets for your tests and make sure to also test the worst case. After all, users that manage large amounts of data with your app are often your best customers and you don’t want to offend them.


For the remainder of the talk, Michael focused on the following three performance characteristics:


  1. Launch Quickly/Avoid Blocking Work
  2. Minimize Memory Usage
  3. Draw Efficiently
In his words, if you optimize these, you’re probably more than half way there on the road to a well-performing app.


1. Launch Quickly



Ask yourself, is this operation essential for loading the most basic main UI? If not, defer it.
Many iOS apps are used for just a few seconds at a time and (even in times of multitasking) relaunched very often. Nothing is more frustrating to the user than having to wait for several seconds each time an app is launched until she can get started interacting with it.


Your goal should be for your app to be ready for user input as quickly as possible. If that means that some UI components (e.g., images or web content) have not yet loaded completely, no problem. It is much better to already display a content-less UI than forcing the user to wait until everything is 100% complete.


To minimize your launch time, try to identify the minimum possible stuff your app must do in all methods that can get invoked before your app’s UI is ready:


  • application:didFinishLaunchingWithOptions:
  • applicationWillEnterForeground:
  • applicationDidBecomeActive:
  • application:openURL:sourceApplication:annotation:
  • application:didReceiveRemoteNotification:
  • application:didReceiveLocalNotification:
  • Your root view controller’s init and awakeFromNib methods

  • viewDidLoad and

  • viewWill/DidAppear:, also in your your root view controller
Your main focus should probably be on application:didFinishLaunchingWithOptions: and viewDidLoad.


Common problems you should look out for are synchronous network calls and the synchronous loading or parsing of large data sets such as huge data files or images. dispatch_async() should be your biggest friend here. You should never block the main thread with a synchronous operation that could potentially take a long time (more than a few tenths of a seconds or so) to complete, much less so during your app’s launch phase. Try to push these operations onto background queues from where you notify the main queue once the operation has finished. Until then, display a progress indicator in your UI.


Instruments: Time Profiler



The Time Profiler instrument in Instruments


To identify problems of this kind with Instruments, use the Time Profiler instrument. While your app runs, Instruments will continuously sample the process and record the code’s current position in the call stack, thereby identifying the methods your app spends the most time in.


When you have collected enough data, stop the recording. Your goal is now to find the most time-consuming methods in the Call Tree and try to optimize them one by one. Here are a few of Michael’s tips:



  • During the recording, place flags in the timeline to identify events such as the end of the launch process.


  • Before looking at the Call Tree, always define an Inspection Range, using the buttons in the toolbar. Use the flags you have placed during recording to identify the section of your code you want to analyze.


  • Uncheck the Invert Call Tree and Hide System Libraries options. The former makes it easier to understand the call tree (though it might take you a little longer to click through to the offending method) and the latter shows you time-consuming method calls in the system frameworks. If you decide to hide those, you might overlook an ill-placed synchronous network call that increases your launch time by several seconds.


    Time Profiler options


  • Now it is time to explore the call tree. Starting with main(), click yourself through the tree until you reach your own code (often more than a dozen levels deep in the tree) and try to find the methods that take up a large percentage of the total time. Open the Extended Detail View to see more info about the selected line or double-click one of the methods in your code to jump directly from Instruments into the code.


    Time Profiler Call Tree


  • Pay special attention to the Self column. It tells you how much time was actually spent right inside that method as opposed to methods further down the stack that the selected method has called. A high percentage in Self often indicates a problem in your code such as a long-running loop.


  • Rule of thumb: At best, launching your app should not take more than 2-3 seconds. If your own code’s percentage of the total launch time exceeds 20-30%, that could indicate a problem.

2. Minimize Memory Usage



Optimizing memory usage has been with iOS developers from the beginning. Because iOS devices have no swap file, there is a hard limit on the memory the system can use. If apps require more memory than is available, the OS has no other choice than to kill one or more apps, starting with (but not limited to) those that are currently in the background. There is another incentive to use as little memory as we can: the less memory your app uses, the longer the OS will keep it alive in the background, which means a better experience for your users.


In iOS 5, if your app is the frontmost app and we send you a memory warning, it means that your head is already on the chopping board.
To notify you of memory pressure, the OS can send a memory warning to the active app. Michael noted that he got the impression that, with the introduction of multitasking in iOS 4, many apps got a bit lazy when it comes to dealing with memory warnings. After all, if the frontmost app did not free any memory in reaction to a memory warning, the OS still wouldn’t kill it as there were usually several apps frozen in the background that could be killed first. And this is indeed the behavior in iOS 4. Because of these unintended consequences, Michael stressed that Apple has made changes in iOS 5: In iOS 5, if your app is the frontmost app and we send you a memory warning, it means that your head is already on the chopping board. So you better do something about it and react to those memory warnings!


Instruments: Allocations + Leaks + VM Tracker + Activity Monitor



Michael has constructed his own combination of instruments to measure an app’s memory usage by combining the Allocations, Leaks, VM Tracker and Activty Monitor instruments. It’s easy to do this yourself by creating a blank instrument and dragging the four mentioned instruments from the Library inspector pane into the main window. You can then save this instrument as a template for reuse.


Michael Jurewitz's collection of instruments for memory usage measurement


The Activity Monitor can be used to compare your app’s resource usage to other apps that are currently in the background. For example, sort the app list by Real Memory and select the Track inspection head option. When you now drag the inspection head across the timeline you just recorded, you will see your own app rise (or fall) among the other backgrounded apps depending on how much memory you use.


Activity Monitor options


The Allocations instruments can be misleading because it does not show you all of the memory your app is using (only the malloc-type allocations). Still, you should watch out for the following patterns:



  • A constantly growing allocations chart is obviously a bad sign. It usually indicates some pretty severe memory leaks. If you don’t fix this problem, your app will crash sooner or later because it runs out of memory.


  • When you repeat the same action in your app over and over again (for example, switch to another screen and then go back to the first screen), your memory usage should not increase. On the allocations chart, you would usually see an increase the moment you start the action (because a new view has to be created) and a decrease soon after going back to the starting position, especially when you repeat this action for multiple times.


    If you see none or only part of the memory being reclaimed, it is a sign of a leak or a block of abandoned memory, possibly caused by a retain cycle. Use Instrument’s Heapshot Analysis to track these problems down. By clicking Mark Heap between repeating the same action, Instruments can show you exactly which objects did not get freed. Looking at the list can usually get you on the right track.


    Heapshot analysis options for the Allocations instrument


  • Large memory spikes can cause the system to evict read-only pages from memory because the OS knows it can read them back later from disk. Because your app’s code is also part of these reloadable read-only pages, a large memory spike, even for just a few milliseconds, can cause your app to stutter when the system evicts and later has to reload your app’s code. Try to avoid them if you can.

VM Tracker, *Resident/\*Dirty\** offers the most accurate view of the real memory your app is using.
The VM Tracker instruments is useful because it can show you the real amount of memory your app uses. The two columns, Resident Size and Dirty Size, show the amounts of memory that can be mapped out to disk or not be reclaimed, respectively. According to Michael, the amount shown in the *Dirty* row and Resident Size column is the most accurate view of the memory your app is using at the point that is currently selected in the timeline. Don’t be stunned if this is radically different from the value that is indicated by the Allocations instrument.


3. Draw Efficiently/Inefficient Drawing



Rule of thumb: if more than 50% of the screen area is made up of transparent layers, scrolling will likely begin to stutter.
The Core Animation instrument can help you identify performance issues in your drawing code. By color-coding the layers in your app’s UI, you get a quick at-a-glance overview how you are doing.


Core Animation instrument options


Even if your app’s UI just consists of plain UIKit controls and you don’t actually draw any content yourself, it is possible to make mistakes that can significantly affect performance, especially during scrolling. According to Michael, you should especially watch out for:



  • Too much transparency: Non-opaque layers make the blending work for the GPU a lot harder so you should avoid them whenever you can. Sure, there are many nice effects that rely on transparency, from drop shadows to rounded corners, but at the end of the day your users will probably appreciate an app that scrolls without stutters more than one that looks a little prettier but doesn’t scroll smoothly.


    Michael especially stressed the construction of text labels in custom table cells. Most labels can stay opaque with a fixed background color because the table view will actually take care to switch the background color when the cell is selected. And if you do need transparency, at least make sure that two transparent labels do not overlap (which multiplies the work for the GPU).


    The Core Animation instruments highlights transparent layers in red (bad) and opaque layers in green (good).


  • Drawing scaled content: Scaling images causes extra work for the graphics system that can usually be avoided by making your app’s resources the correct size. If your app downloads dynamic content from the web, it probably makes sense to downscale images programmatically to the exact size in which they are displayed.


    The Core Animation instrument highlights scaled content in yellow. Not all scaling is bad, though. The Core Animation instrument will also highlight many standard UIKit elements such as navigation bars and tab bar because they use stretched images for their backgrounds. That is absolutely fine.


  • Drawing pixel-misaligned content: Although the coordinate system in Core Graphics uses floating point units, these coordinates have to mapped to a device’s fixed pixel grid before they are displayed. Make sure to draw your content on integral coordinates to avoid aliasing work for the graphics system, which both costs performance and makes your graphics look bad. The Core Animation instrument highlights misaligned content in magenta.

Also have a look at Cyril Godefroy’s blog post about the same talk at the iOS Tech Talk in London.

2011年10月19日星期三

How To Debug Memory Leaks with XCode and Instruments Tutorial

How To Debug Memory Leaks with XCode and Instruments Tutorial:

http://www.raywenderlich.com/2696/how-to-debug-memory-leaks-with-xcode-and-instruments-tutorial

No matter how well you understand memory management in Objective-C, from time to time you’re bound to make mistakes. But often there’s way too much code to search line-by-line for problems (unless you want your hair to turn gray!)
Luckily, Apple has provided some great ways to help you find memory-related problems in your applications. Sometimes these tools scare new developers, but they’re actually pretty awesome and easy to use!
That’s what this tutorial is all about. You’ll get hands hands-on experience using XCode and Instruments to debug and detect memory related problems.
This tutorial assumes you are familiar with memory management in Objective-C. If you are still shaky on the subject, you may wish to read the memory management tutorial first.

Getting Started

Our goal in this tutorial is to check for and resolve any memory leaks in an example app that illustrates common memory-related mistakes. So to get started, download a leaky app that I’ve put together for this tutorial.
Open up the app and run it in XCode. You’ll see a list of sushi in a table view. Try selecting several rows, and then – BOOM! You get the dreaded EXC_BAD_ACCESS error, and the debugger is no help:
The dreaded EXC_BAD_ACCESS
This can be very frustrating to many beginning developers, as it’s not clear where the problem is. Here’s the advice I generally give to developers when you hit an EXC_BAD_ACCESS error:
  1. Set the NSZombieEnabled argument in your executable options, which sometimes helps narrow down the cause
  2. Run with Apple Instruments such as Leaks to look for memory issues
  3. Set a breakpoint in your code and step through until you narrow down where it’s crashing
  4. Tried and true “comment out code till it works” then backtrack from there :]
So let’s try this out for ourselves by trying option #1 – turning on NSZombieEnabled.

Zombie Invasion!

Unfortunately, the NSZombieEnabled option has nothing to do with the zombie apocalypse, so you can put away your boomsticks and chainsaws :]
Sorry, it's not those kind of zombies!
Sorry, it's not those kind of zombies! Image credit: werewolf from sxc.hu.
NSZombieEnabled is a flag that you can enable that will provide a warning when you try to access an object that has been deallocated. And since accessing deallocated memory is one of the most common reasons for crashing, this is a good thing to try first.
To set this up, expand the Executables group in your sidebar in XCode, and double click the PropMemFun executable. Select the Arguments tab, go to the “Variables to be set in the environment” section, and click the Plus button. Set the name of the variable to NSZombieEnabled, and set the value to YES, as follows:
How To Turn On NSZombieEnabled
Now run the app, and click on a few rows again until it crashes. Go to your console log, and you’ll see the following message:
2011-02-03 12:07:44.778 PropMemFun[27224:207] *** -[CFString respondsToSelector:]: message sent to deallocated instance ... 
The program will also halt on the exact line where it’s crashing now. You can go up the backtrace to find the exact line where it’s crashing by selecting the first area in the backtrace that is your code – in this case tableView:didSelectRowAtIndexPath.
Crash Location found with NSZombieEnabled
Aha! Now you know that in this line, a message is being sent to a deallocated string. This line uses two strings: _lastSushiSelected, and sushiString.
Well sushiString looks OK, because it’s initialized with stringWithFormat (which returns an autorelease variable), so it should be safe to use until the next run loop. But what about _lastSushiSelected?
_lastSushiSelected was set the last time this method was run to sushiString. But sushiString is an autorelease variable, so at some point it will be released, and the memory will be deallocated. But then _lastSushiSelected would still be pointing to deallocated memory! Which explains the problem – sending a message to deallocated memory causes a crash.
So to solve this, we just need to retain _lastSushiSelected so that the memory doesn’t go away. So replace the last line with the following:
_lastSushiSelected = [sushiString retain];
Compile and run your code, and now you should be able to run without crashing!

Build, Analyze, and Recognize

Ok, so we have an app that isn’t crashing – a good first step. But next, we need to start making sure that it isn’t leaking any memory.
There’s an easy way to perform an initial first-glance check on your app to see if it has any memory leaks or other problems – use the built-in Build and Analyze function.
This will make XCode run through your code and look for any mistakes it can automatically detect, and warn you about any potential problems. It doesn’t catch everything, but when it does catch things it’s a really quick and easy way to find out about the problems.
Give it a shot by selecting Build\Build and Analyze. You should see that it detected a memory leak, as you can see below:
Leak found with Build and Analyze
The message says that there’s a potential leak related to the “alertView”. If you look at this line, you’ll see that the UIAlertView was created with a alloc/init (which returns an object with a reference count of 1), but never released! There are several ways to fix this, but one way is to add the following after [alertView show]:
[alertView release];
Go to Build\Build and Analyze again, and you’ll see that there are no remaining issues.

Leaks and Plumbers

Unfortunately, you can’t rely on Build\Build and Analyze to catch everything. There’s one other great automated tool to help you check your app for leaks – the Leaks Instrument.
Let’s try it out. Go to Run\Run with Performance Tool\Leaks, and select a few rows in the table view. Also scroll up and down the table view from the top of the table to the bottom of the table. After bait of experimentation, you should start seeing some leaks popping up in the Leaks tab, which show up as blue bars.
Click the stop button, then go to the toolbar in the middle and click it to change from “Leaked Blocks” to “Call Tree”. In the panel in the lower left, click “Invert Call Tree”, and “Hide System Libraries”. You’ll see that it found two different methods in the code with memory leaks, as you can see below:
Leaks found with Leaks Instrument
If you double click a function name, it will take you directly to the line of code that creates the object that was leaked. This should give you a really good hint where the problem lies, and if you examine the code and think about it a bit, you should be able to figure out what the problem is and fix it.
So why not take a look at the code and see if you can figure out what the problem is and fix it? Once you’ve made a fix, you can run Leaks again and verify that the leak no longer occurs. Come back here once you’ve finished, or gotten stuck!

…waiting…

…waiitng…

…waiting…

Tomato-San is angry!
Tomato-San is angry!
What?! Are you still reading here?! You can do it – go ahead and try! :]

The Solution

tableView:didSelectRowAtIndexPath
Leaks tells us that the string created and stored into sushiString is leaked. So let’s think through why this is, step by step:
  1. When sushiString is created, it’s created with stringWithFormat. This returns an object with a retain count of 1, and a pending autorelease.
  2. In the last line in the method, you call retain on the sushiString (bumping the retain count up to 2) and store it into _lastSushiSelected.
  3. Later on, the autorelease takes effect, decrementing the retain count to 1.
  4. Next time the tableView:didSelectRowAtIndexPath method is called, you override the _lastSushiSelected variable with a pointer to a new string – without releasing the old one! So that old string still has a retain count of 1, and is never released.
One solution to this is to add the following line before setting lastSushiSelected to the sushiString:
[_lastSushiSelected release];
tableView:cellForRowAtIndexPath
Just like in the previous method, a string that’s created and stored into a variable named sushiString is leaked. Here’s what’s going on:
  1. A new string is created with alloc/init.
  2. This returns an object with a reference count of 1.
  3. However, this reference count is never decremented, so there’s a memory leak!
This could be solved in one of three ways:
  1. Call release on sushiString after setting the textLabel to the string.
  2. Call autorelease on sushiString after creating it with alloc/init.
  3. Instead of using alloc/init, use stringWithFormat, which returns a string already marked as autorelease.
Plumb the Leaks!
Fix the above two problems, compile and run Leaks again, and verify that you no longer have any leaks in the app!

Where To Go From Here?

Here is a sample project with the updated app, with no leaks or crashes.
At this point, you should have some good hands-on experience with how to find memory leaks in your app using NSZombieEnabled, Build and Analyze, and the Leaks Instrument. You should be able to apply these techniques to your projects to help find and resolve memory leaks!
If you have any other advice or tips to developers for good techniques to find memory leaks in your apps, please add your thoughts in the comments below!



2. =

2.

Nice Tutorial (again!). Just thought I'd add my $.02.

1) I usually create a separate Target with "<project name>-MEMDEBUG" title. I only use that target fo Memory testing.
This keeps me from accidentally submitting to app store with any flags which would be *REALLY BAD*!!

2) I also add this to my code in didFinishLaunchingWithOptions just to be safe:
Code: Select all
//========================== // !!!!: Make sure NSZombies check is off for release version. //========================== if (getenv("NSZombieEnabled") || getenv("NSAutoreleaseFreedObjectCheckEnabled")) { NSLog(@"NSZombieEnabled/NSAutoreleaseFreedObjectCheckEnabled enabled!"); }


3) I make sure it uses all these Flags:
NSZombieEnabled YES
NSDeallocateZombies NO
NSAutoreleaseFreedObjectCheckEnabled YES
NSDebugEnabled YES
NSHangOnUncaughtException YES
MallocScribble YES
MallocPreScribble YES
MallocGuardEdges YES
MallocBadFreeAbort YES

4) I also have at least 3 Global breakpoints:
obj_exception_throw
-[NSException raise]
malloc_error_break

5) I also recommend going here: http://clang-analyzer.llvm.org/xcode.html
And get latest code analyzer as the one bundled with XCode is usually way behind. It has instructions on and a shell script to
make XCode use the new one you download.

Just a for what its worth.

3. Use NSZombieEnabled for EXC_BAD_ACCESS

When you access a object that already release by system or yourself, App will crash and tell you "EXC_BAD_ACCESS". Sometimes it is not easy for you to find out where it happened.
Then you should try NSZombieEnabled, but it just works in Simulator (^_^).

The way to use NSZombieEnabled is quite easy:
1. Find "Executables" in "Groups & Files", double click or just right click and select "Get Info"
2.Turn to "Arguments" tab
3. Add "NSZombieEnabled" in "Variables to be set in the environment" and set its value to YES
OK, now you need to do is debug your app again and waiting for crash. You will get more info about crash.