Last weekend, thousands of Instagrammers from all over the world got together forWorldwide InstaMeet 11, one of Instagram’s community-organized, real-world meetups.#WWIM11 was our largest and most geographically diverse InstaMeet ever — thousands of Instagrammers from Muscat to Bushwick shared over 100k photos.
With over 300 million people around the world using Instagram every month, 65% of whom live outside the U.S., we’re always working to make Instagram faster and easier to use for people no matter where they are. And since our Android redesign last summer, we’ve continued to make performance improvements that allow us to scale better and faster.
One of our recent improvements was addressing the challenge of rendering long, complex text on Android and how to optimize it for Instagram’s feed scrolling. We hope you can use some of what we learned to make your own apps faster!
Product Requirements and Performance Issues
On Instagram, your feed is composed of beautiful photos, videos, and text. For every photo and video, we display the full caption and the five latest comments. Since people frequently like using captions to tell the story behind their photos, they are often long, complex, and may contain links and emojis.
The main issue with rendering such complex text is the performance hit it creates on scrolling. Text rendering in Android is slow. Even on a new device like the Nexus 5, the initial text drawing step for a complex caption with a dozen lines of text can take up to 50ms, and the text measuring step can take up to 30ms. All of these steps happen on the UI thread, and can cause the app to drop frames when the user is scrolling.
Here are a few tips that have helped us optimize comment text rendering for the Instagram Android app.
Use text.Layout, Cache text.Layout
Android has different widgets to display text on screen, but under the hood, they all use text.Layout to do rendering. For instance, the TextView widget will convert the String to a text.Layout object, and use canvas API to draw the text.Layout object on screen.
text.Layout object is inefficient to create since it measures the text’s height in its constructor. Caching and reusing text.Layout instances can save time. The TextView widget in Android doesn’t provide a set TextLayout method on the TextView, but it’s not hard to write one yourself.
Using a custom view to draw text.Layout manually also has performance advantages: TextView is a general-purpose widget which supports a lot of features. If we just needed to render static, clickable text on screen, things would be much simpler:
- We could avoid unnecessary conversion from SpannableStringBuilder to String. Depending on whether your text has links in it, TextView under the hood may do a copy operation on your string, which would cause a lot of allocations.
- We could always use StaticLayout, which is slightly faster than DynamicLayout. We could avoid all unnecessary logic in TextView: the logic to watch changes in text, the logic to properly layout the embedded drawable, the logic to draw the editor, and the logic to pop up a drop-down list.
- Using TextLayoutView, we can now cache and reuse the text.Layout, instead of spending 20ms every time when we call TextView#setText(CharSequence c).
Warm up the Layout Cache after We Download the Feed
Since we know we are going to show these comments after we download them, a simple improvement we made was warming up the cache after we download all of stories.
Warm up TextLayoutCache after You Stop Scrolling
After being able to cache the text.Layout, we get the constant measure time and binding time. But the initial drawing time is still relatively long. The 50ms drawing time results in noticeable jitters.
Most of these 50ms times were taken by measuring text advances and generating text glyphs. They are all CPU operations. To improve text rendering speed, Android introduced TextLayoutCache in ICS to cache these intermediate results. It is an LRU cache and cache keys are text lines. If you hit the cache, the text drawing speed would be much faster.
During our test, the cache could reduce the text drawing time from 30ms-50ms to 2ms-6ms.
To get good drawing performance, we can warm up this cache before we draw the text on screen. The idea is virtually drawing this text on an off-screen canvas. This way we can warm up the TextLayoutCache on a background thread before we draw the text on screen.
The size of TextLayoutCache is default to 0.5M, which is large enough to hold all comment text for a dozen photos. We decided to warm up the cache as soon as user stops scrolling, we look ahead and warm up five stories on the current scrolling direction. At anytime, we have at least five stories in cache on each direction.
After applying all these optimizations, the number of dropped frames was reduced by 60% and the total number of jitters was reduced by 50%. We hope you can apply some of these learnings to your own apps to improve speed and performance. Let us know what you think — we look forward to hearing about your experiences!