Yesterday +Dianne Hackborn posted to Google+ an article that dismissed the common accusation that Android is laggy because UI rendering wasn’t hardware accelerated until Honeycomb:
https://plus.google.com/105051985738280261832/posts/2FXDCz8x93s
It’s an insightful post that illuminates many of the complex issues with smooth Android rendering. Unfortunately, it doesn’t answer the fundamental question asked by both technical and nontechnical android users:
Why is Android laggy, while iOS, Windows Phone 7, QNX, and WebOS are fluid?
This post will attempt to answer that question.
However before I jump in, a couple disclaimers. First, I am a 3rd year undergraduate software engineering student. I interned on the Android team, and +Romain Guy who was responsible for much of the hardware acceleration work in Honeycomb, reviewed some of my code, but I was not on the framework team and I never read the Android rendering source code. I do not have any authoritative Android knowledge and I cannot guarantee what I say here is necessarily 100% accurate, but I have done my best to do my homework.
Second, I’m interning with the Windows Phone team starting in January, so it’s possible that this post will be unconsciously biased against Android, but if you ask any of my friends, it’s really hard to shut me up about Android. I have more Android t-shirts than days of the week and I’d rather give away my Macbook than my Nexus S. The Googlplex is like a second home - I’ve slept there on more than a few occasions to the dismay of startled janitors (and if you ever get a chance to visit, the banana french toast at Big Table Cafe is to die for). If anything, I’m probably biased in Android’s favor.
Finally, any opinions expressed in this article are solely my own and do not represent those of any past or future employers.
With that out of the way, lets dive right in.
Dianne starts off her post with a surprising revelation:
“Looking at drawing inside of a window, you don’t necessarily need to do this in hardware to achieve full 60fps rendering. This depends very much on the number of pixels in your display and the speed of your CPU. For example, Nexus S has no trouble doing 60fps rendering of all the normal stuff you see in the Android UI like scrolling lists on its 800x480 screen.”
Hun? How can this be the case? Anybody who’s used a Nexus S knows it slows down in all but the simplest of ListViews. And forget any semblance of decent performance if a background task is occurring, like installing an app or updating the UI from disk. On the other hand, iOS is 100% smooth even when installing apps. But we know Dianne isn’t lying about the potential CPU performance, so what’s going on?
The Root Cause
It’s not GC pauses. It’s not because Android runs bytecode and iOS runs native code. It’s because on iOS all UI rendering occurs in a dedicated UI thread with real-time priority. On the other hand, Android follows the traditional PC model of rendering occurring on the main thread with normal priority.
This is a not an abstract or academic difference. You can see it for yourself. Grab your closest iPad or iPhone and open Safari. Start loading a complex web page like Facebook. Half way through loading, put your finger on the screen and move it around. All rendering instantly stops. The website will literally never load until you remove your finger. This is because the UI thread is intercepting all events and rendering the UI at real-time priority.
If you repeat this exercise on Android, you’ll notice that the browser will attempt to both animate the page and render the HTML, and do an ‘ok’ job at both. On Android, this a case where an efficient dual core processor really helps, which is why the Galaxy S II is famous for its smoothness.
On iOS when an app is installing from the app store and you put your finger on the screen, the installation instantly pauses until all rendering is finished. Android tries to do both at the same priority, so the frame rate suffers. Once you notice this happening, you’ll see it everywhere on an Android phone. Why is scrolling in the Movies app slow? Because movie cover thumbnails are dynamically added to the movie list as you scroll down, while on iOS they are lazily added after all scrolling stops.
EDIT:[Several people (+Chi-Ho Kwok and +Brent Royal-Gordon especially) have taken the time to explain some mistakes I made in my description of iOS. The fundamental distinction between Android and iOS rendering I identified still stands, but I made some over simpliifcations in my description of iOS because I wasn't familar enough with the workings. I'll let +Brent Royal-Gordon explain:
"The iOS description here isn't quite accurate. There are several things at work:
1. Compositing and previously set-up animations—all the stuff that involves the Core Animation rendering layer tree—do indeed happen on a background thread.
2. Drawing new content into Core Animation layers and setting up their animations happens on the main thread. This is the same thread that user interface actions occur on.
3. In naively written code, all developer-written code would occur on the main thread. However, Apple provides very easy APIs (Grand Central Dispatch and NSOperation) to move things into system-managed background threads. In iOS 5, you can even declare that a Core Data (object-relational database) context cannot be used directly on the main thread.
All that stuff you noticed—the way images aren't drawn into lists while you're scrolling, the way WebKit rendering stops when the system is tracking a touch—isn't inherently built-in by a mechanism that pauses the world when a finger is on the screen.* It's deliberate behavior painstakingly implemented by the developer of each individual app.
This is not a technical difference; it's a cultural difference. Good iOS developers don't ship software until it runs at something near 60 fps while scrolling and tracks touches almost perfectly; good Android developers do.
* This isn't strictly true: the main thread is put into a special mode during touch tracking, and by default, certain callbacks are delayed in that mode. However, a lot of other things, like loads from disk or network activity kept completely on a background thread, are not paused; nor is anything automatically paused during momentum scrolling. The developer has to explicitly delay those things." ]
Other Reasons
The fundamental reason Android is laggy is UI rendering threading and priority, but it’s not the only reason. First, hardware acceleration, despite Dianna’s reservations, does help. My Nexus S has never been snappier since upgrading to ICS. Hardware acceleration makes a huge difference in apps like the home screen and Android market. Offloading rendering to the GPU also increases battery life, because GPUs are fixed-function hardware, so they operate at a lower power envelope.
Second, contrary to what I claimed earlier, garbage collection is still a problem, even with the work on concurrent GC in Dalvik. For example, if you’ve ever used the photo gallery app in Honeycomb or ICS you may wonder why the frame rate is low. It turns out the frame rate is capped at 30 FPS because without the cap, swiping through photos proceeds at 60 FPS most of the time, but occasionally a GC pause causes a noticeable “hiccup”. Capping the frame rate at 30 fixes the hiccup problem at the expense of buttery smooth animations at all times.
Third, there are the hardware problems that Dianne discussed. The Tegra 2, despite Nvidia’s grandiose marketing claims, is hurt by low memory bandwidth and no NEON instruction set support (NEON instructions are the ARM equivalent of Intel’s SSE, which allow for faster matrix math on CPUs). Honeycomb tablets would be better off with a different GPU, even if it was theoretically less powerful in some respects than the Tegra 2. For example, the Samsung Hummingbird in the Nexus S or Apple A4. It’s telling that the fastest released Honeycomb tablet, the Galaxy Tab 7.7, is running the Exynos CPU from the Galaxy S II.
Fourth, Android has a ways to go toward more efficient UI compositing. On iOS, each UI view is rendered separately and stored in memory, so many animations only require the GPU to recomposite UI views. GPUs are extremely good at this. Unfortunately, on Android, the UI hierarchy is flattened before rendering, so animations require every animating section of the screen to be redrawn.
Fifth, the Dalvik VM is not as mature as a desktop class JVM. Java is notorious for terrible GUI performance on desktop. However, many of the issues don’t carry over to the Dalvik implementation. Swing was terrible because it was a cross platform layer on top of native APIs. It is interesting to note that Windows Phone 7’s core UI is built in native code, even though the original plan was to base it entirely on Silverlight. Microsoft ultimately decided that to get the kind of UI performance required, the code would have to be native. It’s easy to see the difference between native and bytecode on Windows Phone 7, because third party apps are written in Silverlight and have inferior performance (NoDo and Mango have alleviated this problem and the Silverlight UIs are generally very smooth now).