February 8th, 2016
Instruments is a performance measurement tool that ships with Xcode. Instruments is a powerful tool, but it can feel intimidating to use. In this article I’ll introduce you to Instruments and show you how to check for memory leaks and measure your application’s memory usage.
Remember that Instruments is a complex application. Don’t get discouraged if you don’t understand everything about Instruments after reading this article. It may take reading this article multiple times and using Instruments for a while for everything to sink in.
I’m using Xcode 7 in this article. If you’re running an earlier version of Xcode, what you see on your screen may not match my screenshots.
Before profiling your application with Instruments, make sure the Generate Debug Symbols build setting is set to Yes in Xcode. If your project does not generate debug symbols, you will see memory addresses in Instruments instead of the names of your application’s functions. To access your project’s build settings,
Generate Debug Symbolsin the project editor’s search field.
If you are profiling an iOS application on an iOS device, connect the device to your Mac.
In Xcode choose Product > Profile to build your project and profile it in Instruments. When Instruments launches a window opens to choose a profiling template. If you want to follow along with me, choose the Leaks template and click the Choose button. The Leaks template checks your application for memory leaks and measures the application’s memory usage.
When you click the Choose button, an empty trace document window opens.
Running from top to bottom, the main areas of the trace document window are the following:
For this article the only toolbar items you need to worry about are the buttons to start/stop recording and pause recording on the left side of the toolbar. The toolbar also has controls to choose an application to profile, add an instrument to the trace, and show/hide different parts of the trace document window.
The instrument list shows the instruments being used in the trace along with a graph. There’s no graph in the screenshot because I didn’t start recording, but when recording starts, the graph will appear.
The jump bar allows you to choose what to show in the detail view. The detail view shows the profiling statistics.
The configuration section is to the right of the detail view. It has three sections: record settings, display settings, and extended detail view. The record settings let you configure what the instrument records. The display settings let you configure what the instrument shows in the graph and detail view. What you can configure depends on the instrument. As an example the Leaks instrument has an option to gather the leaked memory so you can see its contents. The extended detail view shows the call stack for most instruments.
Now it’s time to start profiling the application. Click the Record button on the left side of the toolbar. Your application will launch. Use your application. When you are finished, you can either click the Pause button or the Stop button.
If you want to save what you profiled so you can look at it later, choose File > Save.
Once you start recording Instruments fills the detail view with data and creates a graph for each instrument. In this section I want to focus on the graph. If you move the mouse cursor over the timeline above the graphs, Instruments shows a tooltip with a piece of data about the graph, which you can see in the following screenshot:
The tooltip text isn’t real clear in the screenshot, but it says the application is using 13.23 MB of memory and has 94 new memory leaks.
You can focus on a specific time interval in the graph by setting an inspection range. Click inside an instrument’s graph and drag to create the inspection range. The inspection range is shaded blue in the following screenshot:
When you set an inspection range, the statistics in the detail view change to reflect the time interval in the inspection range. Clicking in the graph outside of the inspection range you set clears the inspection range.
Now it’s time to look at data for specific instruments. Let’s start with the Allocations instrument. If you select the Allocations instrument from the instrument list, the detail view shows a summary of the memory allocation data. The most important data is at the top.
To determine how much memory your application is currently using, look at the Persistent Bytes column for the category All Heap Allocations. In the screenshot I’m using 4.71 MB of memory, which is good for a Mac or iOS application. The Allocations instrument does not record OpenGL/ES texture memory. If your application allocates texture memory, your actual memory use will be higher than what Instruments reports.
The Allocations instrument has the following columns of data for each memory category:
Suppose your application makes 20 memory allocations of 1000 bytes and frees the memory for 15 of the allocations. The Allocations instrument would report the following information:
# Persistent: 5 Persistent Bytes: 5,000 # Transient: 15 Total Bytes: 20,000 # Total: 20
If your application allocates a high amount of memory, the first thing you’ll want to know is where your code is making the big memory allocations. To find where you’re allocating memory, switch to the call tree view. In the jump bar, click on Statistics and choose Call Trees to switch to the call tree view.
When you switch to the call tree view, you should configure Instruments to make finding your code easier in the call tree view. Click the middle button at the top of the configuration section to show the display settings. Select the Invert Call Tree and Hide System Libraries checkboxes. Inverting the call tree brings the memory allocating functions to the top. Hiding the system libraries hides Apple’s code, leaving your code in the call tree view.
After selecting the checkboxes, the call tree view should show your code. For code you wrote, the icon next to the function name will be a white image of a person on a black background.
Option-clicking a disclosure triangle next to a function in the call tree view expands its subtree so you don’t have to be constantly clicking disclosure triangles. Option-clicking again on the expanded disclosure triangle contracts the subtree.
For each function in the call tree, the Allocations instrument shows the bytes used and the allocation count. The bytes used is represented both as an amount and a percentage of the total memory allocated. The statistics may be hard to read in the screenshot, but the following is the listing for the second line in the call tree:
922.92 KB 5.7% 6173
This line says this particular function and any functions in its subtree allocated 922.92 KB of memory, which is 5.7% of the total allocated memory. This function and any functions in its subtree made 6173 allocations. The subtrees keep Instruments from telling you the exact amount of memory each function allocates. Inverting the call tree reduces the subtree, which makes the listing for a function in an inverted call tree the closest approximation to the amount of memory that function is allocating. If you set an inspection range in the graph, the listing reflects the amount of memory allocated in the inspection range’s time interval.
For most Mac and iOS applications, the first listing in the call tree view is the
main function. This function is going to have a high amount of bytes used and allocations. If you don’t invert the call tree, the
main function will show 100% bytes used. That’s because
main is the starting point of your application. Every function in your application will have
main in its call stack so every memory allocation your application makes will have
main in its call stack.
When trying to figure out where your application allocates memory, don’t worry about the
main listing. The
main function doesn’t do much in most Mac and iOS applications. There isn’t much you can do in the
main function to reduce your application’s memory usage.
Double-clicking a function in the call tree view shows the source view. For the Allocations instrument, the source view shows you the lines of code that allocated the memory along with the percentage of memory allocated. The percentage is relative to the function. If a line of code says 80%, it means that line of code allocated 80% of the memory the function allocated.
Now it’s time to look at the Leaks instrument. The graph will tell you if you have any memory leaks in your application. A memory leak occurs when your application allocates memory and never frees the memory. The following screenshot shows an example of the graph for the Leaks instrument:
The screenshot shows three checks for memory leaks. In the first check Instruments found new memory leaks so it marks the graph with a red mark with an X in it. In the next two checks, Instruments did not find any new leaks so it marks the graph with a gray mark with a dash in it. If Instruments finds no memory leaks in your application, it marks the graph with a green checkmark.
If Instruments finds memory leaks in your application, the next step is to find where the leaks are occurring in your code. The steps to do this are the same as for the Allocations instrument: switch to the call tree view by using the jump bar and select the Invert Call Tree and Hide System Libraries checkboxes. Selecting the checkboxes will limit the call tree listing to functions you wrote.
For each function in the call tree listing, Instruments shows the bytes used and the number of leaks. Like the Allocations instrument, the bytes used is represented both as an amount and a percentage, but in the Leaks instrument the percentage represents the percentage of total leaked memory. The statistics may be hard to read in the screenshot, but each function listing has the same data, which you can see below.
3.56 KB 100% 94
The listing says there are 94 memory leaks totaling 3.56 KB of memory. The 94 leaks represent 100% of the leaked memory. In my trace the leaked memory is being allocated in one place, the function at the top of the call tree listing. Double-clicking that function shows the lines of code that allocated the leaked memory. Keep in mind that the leaked memory is allocated in one place only in my trace example. Your application may leak memory in multiple places. Double-click any functions in your call tree view to see where the leaked memory is allocated.
The fact that a function allocates leaked memory doesn’t necessarily mean that function is responsible for leaking the memory. But it’s a good place to start looking for the source of the leak.
I want to summarize the most important points to take away from this article. These points apply to most instruments, not just the Leaks and Allocations instruments.