This article shows how to set up and use the profiler that is included in Xamarin Studio and Xamarin.iOS. Using a given sample, we’ll run this tool through its paces, looking at memory and performance analysis. Then we’ll use the profiler to determine how objects are retained in memory and examine some clues about reference types.
The MonoTouch profiler is a very useful tool that’s included in Xamarin Studio 2.8.5 and Xamarin.iOS 5.1.1 (for profiling to work, install these or subsequent versions of Xamarin Studio and Xamarin.iOS). We’re going to look at many of the profiler’s features, including memory and performance analysis, and how to use this tool to help us manage objects in memory. We’ll be using the SimpleTextInput sample from monotouch-samples, available here: https://github.com/xamarin/monotouch-samples/tree/master/SimpleTextInput.
You can also refer to the the Finding Native Object Memory Leaks in Xamarin Apps with Instruments video for more information on the profiler.
You need at least Xamarin Business Edition to use the MonoTouch Profiler. This is not supported in Xamarin.iOS for Visual Studio.
Setting up the Profiler
First, download the sample and open the solution in Xamarin Studio.
To start profiling, use the Debug|iPhoneSimulator configuration, and then select Project -> Profile – Mono as shown in this screenshot:
Before we begin profiling, we must enable these project options: debugging, profiling, and the SGen garbage collector. If we don’t enable these, Xamarin Studio prompt us with this dialog:
Note that these options are per project configuration, so if you profile a subsequent project configuration you’ll be prompted again.
Now a dialog opens, asking what to profile and which options should be
enabled. The profiler we’ll use is Mono’s powerful log profiler (http://www.mono-project.com/Profiler). All the options for the log
profiler are available, but early releases of the GUI may only be available for
memory analysis. It is also possible to do performance profiling, but the only
way to analyze the results is to use the
tool shown here:
Here is a quick explanation of the various options:
Heap Analysis is the default profiler mode. It allows us to profile heap usage at specific points in time and to compare heap usage over time.
- On Demand - You’ll have a button to click on in the profiler GUI to request heapshots.
- Major Garbage Collection - Take a heapshot every X major garbage collection.
- Specific Time - Take a heapshot every X milliseconds.
With this mode you can see where the app is spending CPU time.
- Statistical Sampling - Take a sample of the call stack with a certain frequency, which will give you a statistical view of where your app spent time - this mode doesn’t have very high overhead (depends on the sampling frequency of course), so it’s a quick way of getting an initial view of CPU usage.
- Record enter/leave events - This will record every method enter/leave, together with a time stamp. This is much more exact that statistical sampling, but it also has a significant overhead, so the app will be a lot slower in this mode.
- Track Each Object Allocation - Record a stack trace every time an object is created.
- Max frames - Sets the maximum number of frames collected when a stack trace is logged.
- Output Filename - this is the name of the file where the profiler will store its data (for both simulator and device profiling). Have in mind that for performance analysis (in particular when recording enter/leave events), this file will grow very quickly.
- Specify options manually - not a target for this guide, but for those interested there is more information here: http://www.mono-project.com/Profiler.
Now go back to the
Memory Analysis tab and click on the
Profile button. The app will build and run in the simulator, and at
the same time the HeapShot GUI will show up:
As mentioned before, you have a button to request heapshots. Clicking it a couple times will produce a few heapshots:
The left hand side of the screen contains an entry for every heap snapshot.
By clicking on one of these snapshots and viewing the
tab, you can quickly inspect a number of metrics such as:
- The types that are being created.
- Number of instance of the objects created (default sorting)
- Memory used by these instances.
- Average size of these objects.
There are two ways to view the information in the heap:
- Viewing which objects the current object references.
- Viewing objects that reference the current type.
The default mode is to display a list of types and as you expand each type
you can see what objects are being referenced by that type and also the quantity
of each object. You can also use the
Filter function at the bottom
to limit the display of types to a given type name or namespace.
As you can see, the
SimpleTextInput.AppDelegate references a
MonoTouch.UIKit.UIWindow and a
SimpleTextInput.SimpleTextInputViewController, and there is one
instance of each type.
To view the objects that keep references to a given type, click on
Inverse references at the bottom of the screen. This will allow you
to see what types reference the current type so you can figure out why something
is being retained in memory.
From this screenshot you can see which types store references to
MonoTouch.UIKit.UIWindow and the quantity of UIWindows each type
retains. This mode is invaluable when trying to figure out why objects you
expect to have been garbage collected are actually still in memory. You can keep
expanding the toggles to see why other objects are being kept alive. To see why
SimpleTextInput.AppDelegates kept alive you can simply expand that
It is possible to examine which objects were created between two heapshots in time. Do do this set the checkbox on the snapshot that you want to use as a reference, and then select the second snapshot.
The results displayed on the GUI will be only for the differences.
In this article, we’ve shown how to set up and use the profiler that’s included in Xamarin Studio and Xamarin.iOS. Using a SimpleTextInput sample, we looked at memory and performance analysis. Then we used the profiler to work with types to help us see how objects are retained in memory. Finally, we explained how to use a Diff view to compare heapshots.