3 Techniques We Used to Improve Skyscanner for iOS PerformancePosted on by Csaba Toth
As developers of Skyscanner’s iOS app, we’re always thinking about the performance of the application. We know that even if we have a great application with a lot of features, lag can hamper the whole UX. Here are our top three tips to help address app performance.
If we test our application on a high-end device, we should also ensure it runs flawlessly on less high-end devices. We need to keep in mind that we are developing products for a large audience, with different devices. Some people have the most powerful ones, but many use older devices with much lower processing power.
Tip #1: Test on low-end devices, not just high-end devices
Let’s check out the 2016 March usage statistics of the Skyscanner iOS app:
As you can see in the chart, nearly half of users don’t use the latest device types. For example iPhone 5 usage is still close to 10% of all users. We don’t want users turning away from our app because it isn’t running smoothly.
So what can we do when we see that the UI is lagging on low-end devices?
I could say the if you are a pro, just look at the code and do some algorithm tweakings and optimize the hell out of it. But blindly jumping into it wouldn’t make too much sense as you are probably not even aware of what parts need optimisation. So before doing any changes, you need to start measuring the performance and look for bottlenecks.
If something is not measured, you cannot be sure that you are doing the right thing. It is also useful to store measurement results so that they can be reused later for comparison.
Tip #2: Measure the performance
iOS developers have a superb tool to measure the performance of the application in many ways: Instruments. You get it for free as a part of Xcode.
In this application you can choose between several profiling templates, based on what you want to test out.
If you want to know why your app is lagging and what is happening under the hood, you should choose either the ‘Time Profiler’ or the ‘Core Animation’ template. Both of them will tell you when the CPU is processing and what method calls are using the CPU a lot. Basically it periodically captures stack trace information from all the selected processes running on the system which you can analyze later. It is really handy to know where you should start. With the ‘Core Animation’ template, you can also see the current frame refresh rate in addition to the CPU processing times.
After recording a session with the time profiling template, you will see results like those below.
The chart at the top of the window illustrates the CPU utilization: time spent on executing code. You should focus on making the peaks as small as possible. By checking the call tree, it is possible to identify the functions using most of the resources and investigate those methods that are running on the main thread. It also enables you to optimize your methods running on other threads.
Are you questioning why we should focus on the main thread? On iOS, every UI related action must be performed on the main thread. If it is blocked by something, it will affect the performance of the user interface and will result in lagging. Basically, it means that the CPU cannot calculate all frames at a frequent pace.
When we started to optimize the performance of the flights result page in the Skyscanner app, first we measured how it performs on iPhone 5 and 5s devices. It wasn’t as good as we would have liked. After those initial measurements, we discovered that there are several bottlenecks which could be improved easily.
The first one was loading all the accessibility labels and hints. Accessibility labels are sentences describing the elements on the screen so that if someone have reduced sight, the OS can read them out loud.
Generating these texts is not really a heavy calculation task, but when you have 1000 or more results it can become time-consuming. Recalculating non-changing strings over and over it is not the best way to go about this. It is far better to store the string you calculated once and use it when you need it again.
Tip #3: Cache frequently calculated or accessed items
Previously, we calculated the accessibility labels for both people who enabled accessibility and those who hadn’t. So all we had to do was not set the labels for those who don’t have the feature turned on. BOOM, a 8.9% performance boost.
Before the optimisation 13,5% of the calls were spent on getting the localized resources.
After the change, it drastically dropped. Now it attributes only for 4.6% of the CPU calls spent on generating the localized resources.
The second bottlenecks were the date-related calculations (getting the start of the day, beginning of the month, etc…). The exact same calculations were performed over and over in various parts of the app. When you know that you need to calculate something over and over, the best thing is to keep one copy of the data and return the cached object. This will ensure that the app will just do the calculations once, then the calculated values can be reused. We implemented a Date Cache: when a new date calculation is performed, the result is stored in a hash table. This way we don’t have to calculate it again, just return the saved version. This lead to another 3.2% performance gain.
See the filtered call tree in the screenshot. Before the change, we had 3.9% of the CPU time spent on date related calculations.
After applying the caching mechanism, we got much better results; only 0.4% of the time was spent on the NSdate related functions. Of course, you should be aware that storing too many objects could increase the memory usage, but if used correctly, caches are really powerful tools.
Instruments is great for analyzing certain parts of the app, but we also wanted to monitor the performance during the actual usage with real data. Based on a great open source project called RRFPSBar, we created a tool that measures the current/average/minimum FPS. This can be used to investigate how the app is performing while doing any manual testing.
In the screenshot, you can see the FPS counter bar in action. It shows the current frame rate as a chart. If the red bars are higher that means the FPS is dropped. It also shows the average and the lowest FPS values. With every event we log all three current FPS values.
You can see on the picture how the performance optimisation process improved the frame rate. I made a formula to show the percentage of those people who has more than 30 fps when navigating to the day view divided by the app start. We started from 61% and now are at 97.47%.
Logging enables us to see how new features affect the frame rates. Also, we can see if an improvement made the UX more fluid or whether we have performance regression.
These were just three simple techniques we found especially useful, though there are lots of other methods that can help make your product better. Incorporating these in day-to-day work makes performance optimisation and awareness part of the culture, which in turn leads to better apps and more user engagement. It’s a win-win!
Csaba Toth (‘Csacsi’) is a software engineer working on Skyscanner’s iOS application. He’s passionate about hacking the UIs and creating prototypes.
More interesting posts on this topic:
This blogpost is part of our apps series on Codevoyagers. Check out our previous posts: