I spent part of the weekend playing with Instruments and the DTPerformanceSession framework. This framework offers a number of really cool features for fine-tuning performance testing on both iOS and OS X applications. Unfortunately, there's not a lot of documentation out there. In fact, the only thing I could find is the "New Features in Instruments 4.0" user guide.
DTPerformanceSession offers a rich set of features for gathering and recording information from a restricted set of instruments (currently limited to Time Profiler, Allocations, leaks and the Activity Monitor). We can then save the data as a
.dtps bundle, which can be opened by instruments. While this opens up possibilities of creating customized test harnesses--it's honestly too much work for most situations. Fortunately, there's an easier option.
DTPerformanceSession also lets us add custom flags to our app during our regular performance testing. We can then access these flags from within instruments, using them to focus in on a very specific portion of our code.
To add signal flags, first we need to add the
DTPerformanceSession.framework to our application. We then need to import the
DTSignalFlag header file, as shown below:
Note: for OS X, I also needed to manually set the Runtime Search Path. I'm not sure why this wasn't set automatically--it may be because I was using a version of Xcode installed in a non-standard location. If you run into strange errors, simply copy the Framework Search Path location over to the Runtime Search Path in the target's Build Settings.
We can then add signal flags to our app by calling
DTSendSignalFlag(). This method takes three arguments. Honestly, I'm not 100% sure what these arguments do. There's no real documentation--not even in the header file. However, based on the header file and Apple's sample code, The first appears to be a user id string. This string shows up in Instrument's flag table, and can be used to identify particular signals. Apple's sample code uses a unique C string, beginning with the developer's reverse domain name notation.
The second argument defines the type of signal. Here, we can use
DT_POINT_SIGNAL to flag a single location in time. Alternatively, we can use
DT_END_SIGNAL to mark off a block of code.
The third argument determines whether or not it adds a backtrace. All of Apple's samples simply passed in YES, so I did the same.
So, to mark off a section of code, I simply added the following code to my project:
DTSendSignalFlag("com.freelancemadscience.MyProject.importantBlockStart", DT_START_SIGNAL, TRUE);
// The code we're testing goes here...
DTSendSignalFlag("com.freelancemadscience.MyProject.importantBlockEnd", DT_END_SIGNAL, TRUE);
Now, whenever I run instruments, these flags will be added to my project. Unfortunately, they do not show up by default. Instead, we must open the flag table, by selecting Window > Manage Flags….
This will bring up a list of all the flags in this profiling run. The table shows the flag's name (the user id string we used), a timestamp, and for the start/end flags, it will list a duration. We can also expand the start/end flags, if we wish.
To display the flags in Instrument's timeline, click on the Displayed Flags button, and select Symbol Flags.
Even more importantly, we can set Instrument's range filter to the start and end flags by selecting the start flag in the table, then clicking on the cock icon at the bottom of the table.
This will highlight the range between the selected start flag and its corresponding end flag, limiting the profiling information to the method calls and allocations that actually occurred within this range. For example, below, I am viewing the Time Profiler data for the code between the selected start and end flags. During this range, my app spent 92% of its time in the
As you can see, this gives us a lot of fine control over exactly what we're looking at, letting us focus in on a known problem areas.