You are currently viewing 5 Simple Steps to Find Slow Code Using Xcode Time Profiler

5 Simple Steps to Find Slow Code Using Xcode Time Profiler

Xcode Time Profiler is a powerful performance analysis tool provided by Apple. It is specifically designed to help developers optimize the performance of their iOS, macOS, watchOS, and tvOS applications.

If you open the Time Profiler for the first time, you might feel overwhelmed by its complex user interface and the extensive amount of data presented to you. To assist you in getting started, here are five simple steps you can follow to identify the bottleneck in your code:

  1. Profile Your App
  2. Filter the Track Viewer
  3. Simplified the Call Tree
  4. Analyze the Heaviest Stack Trace
  5. Reveal Source Code in Xcode

Now, let’s go through these steps one by one, shall we?


The Sample App

To demonstrate the capabilities of the Time Profiler, I have created a sample app exclusively for this article. This app features a single button that, when tapped, loads two text files repeatedly for a total of 1000 times.

@IBAction func loadFiles(_ sender: Any) {
    
    for _ in 0..<1000 {

        // Load text file with size: 74KB
        let url = Bundle.main.url(forResource: "small_text_file",
                                  withExtension: "txt")!
        let _ = try! String(contentsOf: url)
        
        // Load text file with size: 885KB
        let url2 = Bundle.main.url(forResource: "large_text_file",
                                   withExtension: "txt")!
        let _ = try! String(contentsOf: url2)
    }
}

As you can see, the text files being loaded have different file sizes. We anticipate that large_text_file.txt will take longer to load compared to the small_text_file.txt.

In just a moment, we will use the Time Profiler to verify this assumption and examine the execution times of these operations.


Step 1: Profile Your App

To begin profiling your app in Xcode, navigate to the “Product” → “Profile,” or simply press ⌘ + I. After the build process is complete, Xcode will prompt you to choose a profiling template. In this case, select “Time Profiler” and proceed.

Choosing profiling template in Xcode
Select the “Time Profiler” template

To initiate profiling, you can click on the red color record button on the top right or use the shortcut ⌘ + R. Once the recording session is started, go ahead and trigger all the events in your app that you want the Time Profiler to capture samples of.

After the Time Profiler has collected all the necessary data, you can stop the recording and proceed to the next step.

Start recording a profiling session in Xcode Time Profiler
Start recording a profiling session

Pro Tip:

It is advisable to run the time profiling on a real device for more realistic and accurate results.


Step 2: Filter the Track Viewer

The top portion of the Time Profiler window contains the Track Viewer, which displays the sample data collected throughout the entire recording session.

For our sample app, we are only interested in the section where we tap on the button. Therefore, we can filter the Track Viewer by dragging the mouse across the spike shown in the Track Viewer.

Filter the track viewer in Xcode Time Profiler
Filter the tracker viewer

Pro Tip:

Use ⌥ + scroll to zoom in and out of the track viewer.


Step 3: Simplify the Call Tree

Next, let’s head down to the Call Tree. Don’t be overwhelmed by all the data presented in this section. It may seem like a lot to process, but I can assure you that we will soon see a much-simplified presentation.

Go ahead and click the “Call Tree” button at the bottom of the window and check the following 3 options:

  • Separate by Thread
  • Invert Call Tree
  • Hide System Libraries
Simplifying the call tree in Xcode Time Profiler
Simplifying the call tree

By selecting “Separate by Thread,” the Call Tree will be organized based on different threads, providing a clearer structure. Meanwhile checking the “Invert Call Tree” option will sort the deepest function call at the top of the tree. This enables us to see the function call that is most relevant to us first. Lastly, selecting “Hide System Libraries” will hide all the system-generated stack traces that we don’t really care about.

As a result, the Call Tree is presented in a much more simplified and manageable format. This allows us to analyze the Call Tree in a more effective and efficient manner.


Step 4: Analyze the Heaviest Stack Trace

By utilizing the simplified Call Tree, we are now able to easily pinpoint the thread that experienced the longest execution time. In our case, it happens to be the main thread. By clicking on the main thread node, we can see the “Heaviest Stack Trace” located on the right. This is where we will find the slowest running code of our sample app.

Analyse the heaviest stack track using Xcode Time Profiler
The heaviest stack trace

Next, right-click on the “Heaviest Stack Trace” section and select “Source Location”. By doing so, the time profiler will display the trace’s source code filename and line number.

Analyse the heaviest stack track using Xcode Time Profiler
Showing the source location

Our focus should be on the first trace that references an actual source file, as this is where we can find the location of the slowest-running code.

Analyse the heaviest stack track using Xcode Time Profiler
The first trace that references an actual source file

Go ahead and double-click the trace. The Time Profiler will bring us to the corresponding function call and provide a breakdown of the code execution time.

Viewing the breakdown of the slowest function call using Xcode Time Profiler
Viewing the breakdown of the slowest function call

As you can see, the time profiler has highlighted the section of the code that required a significant amount of time to execute. Specifically, line 33 which loads a small text file took approximately 33ms of execution time, while line 38 which loads a larger text file required around 136ms. This makes total sense as larger files should take longer to load.

With that, we have successfully pinpointed the slowest code segment of our sample app.

Pro Tip:

The Time Profiler will sample what the CPUs are doing at about 1000 times per second. This implies that 1 second is equivalent to 1000 samples. Therefore, 1 sample is equal to 1 millisecond (1ms).


Step 5: Reveal Source Code in Xcode

To begin optimizing the slow-running code, we first need to access the source code in Xcode. We can achieve this by heading back to the Call Tree and locating the corresponding node. Simply right-click on the node and select the option “Reveal in Xcode.”

Reveal the source code from Time Profiler to Xcode
Reveal the source code in Xcode

Voilà, the slow-running code is now highlighted in Xcode and we can now start working on it.


Wrapping up

It is obvious that the sample app used in this article is extremely simple and very easy to analyze. Nonetheless, the main focus of this article should be the steps presented. These steps can be applied to apps of any size and complexity. I encourage you to give them a try and let me know what you think.

If you enjoy reading this article, I think you will like the following articles too:

Be sure to follow me on Twitter and LinkedIn, and subscribe to my newsletter so that you won’t miss out on any of my upcoming iOS development-related articles.

Thanks for reading. 👨🏻‍💻


👋🏻 Hey!

While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.

  • Bartender: Superpower your menu bar and take full control over your menu bar items.
  • CleanShot X: The best screen capture app I’ve ever used.
  • PixelSnap: Measure on-screen elements with ease and precision.
  • iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.