It’s part of our responsibility as developers to ensure that our applications are fast and responsive in order to improve the user’s experience.
We also, however, need to ensure that our apps aren’t so power hungry that they end up draining massive amounts of battery – thus preventing our users from using our apps in the first place!
How can we improve our app’s performance without compromising its usability?
The answer lies in profiling.
Time Profiler is one of the many features of the Instruments tool that ships with Xcode. It gives us, the developers, the information we need to see what our application is doing and when it is doing it.
For the benefit of this post, I’ve created a demo app. Our demo application is an image classifier that uses machine learning to categorize room images. It will give us information that lets us know whether a picture is taken from a kitchen, bathroom or living room.
Let’s take a look at what our application is doing.
From Xcode’s product menu, I’m going to select ‘Profile’. This will create a release build of my app. From there, I’ll launch Instruments so that I can start measuring it.
Getting started with Time Profiler is pretty easy; I just need to press the ‘Record’ button and it’ll both launch and start to measure our application. Once I’ve stopped the ‘Record’ button, I can start interacting with the app in order to analyse all of the information the Time Profiler has given me.
Time Profiler typically gives us two graphs. The first graph concerns CPU usage. By hovering over it, I can see the precise values.
The second graph informs me what lifecycle state my application is in and how long it has been in that state. This gives me a useful overview of what my app is doing and when it is doing it.
How do I know what the application is doing?
In order to figure out precisely what my app is doing, I can call upon another feature – the Call Tree View.
Whilst it’s profiling your app, Time Profiler takes a screenshot of your call stack. Time Profiler, however, doesn’t actually measure duration effectively, as it can’t distinguish between long-running methods and much faster methods that are called upon repeatedly, so be wary of that.
If you’re using really fast functions, or really fast methods that aren’t called upon very often, they won’t appear in your Call Tree View because they’re not having an impact on how much work you have done over time.
It’s important to be mindful that, whilst it’s a useful tool, it doesn’t capture everything!
Let’s take a closer look at what my application is doing. If I use the trackpad to zoom in on the CPU usage, I can see where the hard work is being done. As you can see, I’m seeing the CPU go over 100% which is far from ideal.
Using the aforementioned Call Tree View, we can see the method call and the line of code from my app that’s causing all of this work to happen.
Here’s the culprit…
We can see this particular line of code has 0 self-weight, which means it’s not doing any of the work itself but it’s the piece of code that’s triggering all of the work.
We can also view all of the hard work that’s being done in the main thread.
This particular line of code is processing all of my images; This should be done in the background thread to prevent the main thread for becoming blocked while our request is executed:
This is the line of code that concerns the image classification. Once it’s complete, it displays a label with the room classification. We thus need to update the UI. This is work that has to happen on the main thread so, for the meantime, we need to create our image classification in a background thread.
Once that’s complete, we need to switch it back to the main thread in order to update the UI.
If I profile my app again, now that this stage is complete, I can see that the main thread is free and all of the hard work is being done in the background.
What should I profile?
You ideally want to improve the performance in an area of the app which has a greater impact on your users. Always improve upon functions that your users frequently perform.
As you can see from our demo app, using Time Profiler, I found an area where the CPU was too high and worked to make it much more responsive. I identified the work being done on the main thread, moving work that wasn’t UI related to the background thread, in order to ensure that the app was performing as expected. By focusing on an area of the app that users will see, I’ve made great use of my time and greatly improved upon the user experience.
All thanks to the magic of profiling!
Please note: It’s important to do profile release builds because you want all the optimisations that you’ll get from the compiler. Profiling must be done on the device and preferably on old devices too so you can detect bottlenecks. Preferably focus on responsiveness and look for CPU spikes. Identify things that can be moved from the main thread and look for poorly scaling code (O(n2), etc.)
Time Profiler is a tool that developers can regularly refer to, to collect and visualise sample stack traces, in order to improve the usability of the products we’re making.
Whilst it can seem overwhelming, as there are so many tools available in Instruments, it’s important to understand that each individual tool does a very simple task.
Once you’ve learned what tools are most helpful to you and your work, you’ll be well on your way to creating high-quality apps.
In the next post in this series, I’ll be looking at using machine learning for image classification. That’s something to look forward to!
Are you a fan of the Time Profiler tool? What do you do to improve your iOS app’s performance? Tweet us your thoughts. We’d love to hear them!
If you’re looking for a new mobile tech blog to add to your reading list, you’ve found us! We share weekly posts about app development, from how to develop Android apps for foldable devices to how to use IDEs effectively.
Follow us on Twitter, Medium and LinkedIn to be notified of our future posts!