Facebook adopted a technique known as progressive JPEG to speed up photo loading in its iOS app, describing the process in a post on its engineering blog as an image format that stores multiple, individual “scans” of a photo, each with an increasing level of detail.
As for News Feed in its Android app, another engineering blog post details how the social network handles the extremely complex ListView behind the process.
Highlights of the post on PJPEG follow:
Our team took a look at how we can make photos faster on iOS and we found a way to reduce the data used by Facebook for iOS by about 10 percent and show a good image 15 percent faster than before.
Progressive JPEG (PJPEG) is an image format that stores multiple, individual “scans” of a photo, each with an increasing level of detail. When put together, the scans create a full-quality image. The first scan gives a very low-quality representation of the image, and each following scan further increases the level of detail and quality. When images are downloaded using PJPEG, we can render the image as soon as we have the first scan. As later scans come through, we update the image and re-render it at higher and higher quality.
Rendering images progressively in the Facebook app has some advantages:
- Data consumption: PJPEG allows us to skip downloading smaller versions of an image.
- Network connections: Since we don’t download smaller versions of an image anymore, we now use only one connection per image instead of many.
- Disk storage: Storing fewer photos on disk decreases the amount of disk space used by the app.
- One URL: Since we no longer need to download multiple images at different sizes, we can simply use one URL.
There is a downside to PJPEG: Decoding and rendering the image multiple times at varying scan levels uses more CPU (central processing unit). Decoding images can be moved to background threads, but the process is still heavy on CPU. The real challenge for us was to find the right balance between data usage, network latency and CPU utilization. For instance, we considered using WebP, since it is more optimal in file size than JPEG in some cases, but the format does not support progressive rendering.
We render three different scans of each photo:
- First we render a preview scan: This is pixelated.
- Then we render a scan that looks good to the naked eye. In fact, it looks almost perfect to the naked eye.
- Finally we render at full-quality: the best resolution possible.
The result is that people see a good photo sooner!
And here are highlights of the post on rendering the News Feed in Android:
If you work on an Android app (or any touch-screen-based app, actually), there’s a very good chance you have at least one activity based on a ListView. In many cases, it may be the screen of your app where users interact with the app the most. In the case of Facebook for Android, this ListView is your News Feed. Keeping News Feed working well in Facebook’s Android app presents a variety of engineering challenges, starting with performance considerations for touchscreens.
Facebook’s News Feed, a popular screen in the Facebook for Android app, is an extreme example of a complicated ListView. This makes delivering a smooth scrolling experience especially hard.
First, each story you see in the feed is pretty tall, and contains several pieces of text and photos. This makes the challenge of binding all of the story’s data without skipping frames significantly harder.
Secondly, there are multiple variations of stories in your feed. Aside from a simple story containing some text and a photo, you can see multiple variations of attachments: a preview for a link, an album, a video, etc. The story might be shared, in which case the story contains another story inside it. Hardest of all, we need to render aggregated stories, which means one story can actually be composed of several stories that are related. A good example of this is when many of your friends wish you a happy birthday. These aggregated stories are the most challenging to render, as one of them can easily be twice as tall as the screen of the device.
As an extra challenge, the typical Android phone is not a high-end device. So the amount of computations you can fit in under 16.7 milliseconds is likely less than the amount on the majority of phones you develop on.
About a year ago, we felt it was the time to invest in a new architecture for our rendering code for News Feed. We knew we wanted to avoid the pitfalls of the previous design and try to get rid of our more fragile code. For that purpose we considered a new idea: Splitting each story in the News Feed to several items in the ListView. Using this idea, the header part of a story will be its own item in the ListView. This nice trick results in various advantages right away. First, this makes Android’s recycling effective again. If before, two custom views for a story were too different to have one recycled into the other, now, recycling is happening on a sub-story level.
The other idea we incorporated to the design is decoupling the binding logic from the custom views themselves. For this purpose, we basically split our previous custom view into two classes: a simpler “dumb” custom view, and a Binder class.
This rewrite effort has resulted in many benefits:
- The number of out-of-memory errors experienced by people using Facebook has been reduced by 17 percent, and the number of total errors was reduced by 8 percent. Some errors, such as stack overflows in the view hierarchy, have disappeared.
- The maximum time it takes to render a frame was reduced by 10 percent, after additional optimizations and removal of custom recycling code that was no longer needed. Additional simplifications are expected to improve by such amounts once more. Big jumps that resulted from loading a tall complicated story have disappeared.
- We were able to simplify reusing our feed code with different layouts, which helped in the creation of the stand-alone app for Facebook Groups on Android.
- The new design has lent itself to improving our code quality. More teams can contribute to News Feed while being sandboxed from other teams working on parallel features. We also used this opportunity to make sure our code is testable and well covered. The new feed code has a line coverage with unit tests of 70 percent, compared with 17 percent in the old code.
Readers: Have you noticed any performance differences in your Facebook iOS or Android apps?
Speed image courtesy of Shutterstock.