Saturday, December 10, 2011

The truth about Android & iOS UI performance

Some fallacies related to Android vs. iOS UI performance made the rounds recently, inflicting undue damage to Android's reputation. It all started with a misinformed Google+ update written by former Android testing intern Andrew Munn.

Time reporter Matt Peckham echoed Andrew's misconceptions the following day, further spreading the FUD about Android. Toward the end of his article, Matt noted:

I’m not an Android or iOS software engineer, so all I can say in response to any of this is that, assuming Munn’s correctly articulated the way rendering takes places on Android and iOS devices, it makes sense (but then so does the idea that Lee Harvey Oswald had help, at least to some people).

Peckham makes no mention of trying to corroborate Munn's claims with a more experienced, knowledgeable engineer, like Romain or Dianne from the Android team, nor does he reference the corrections made by iOS experts in the comments on Munn's post. A more qualified engineer would support their theories with evidence like code, specifications, and performance test results, not Reddit and Hacker News comments as Munn did.

I don't claim to have all the answers, but I can tell you that implementing fluid interfaces on both iOS and Android is time consuming and difficult. The challenges are an order of magnitude more complex than Munn suggests. I haven't had an opportunity to try Ice Cream Sandwich yet, so I can't tell you firsthand how it compares to the iPhone. However, Jason Kincaid, quoted by Munn, described ICS as quite smooth and noted that both Android and iOS stutter occasionally.

Now that Android has pervasive hardware acceleration, Android has no fundamental technical disadvantage compared to iOS with regard to to implementing fluid UIs. Given comparable hardware, any UI that runs smoothly on iOS should be able to run just as smoothly on Android. That said, hardware acceleration isn't a magic switch that you can just turn on. Android developers need to modify their applications to take full advantage, just like iOS developers have to design their own applications with hardware acceleration in mind. This will take time.

To Munn's point, "UI thread" and "main thread" mean the same thing. Most user interface frameworks, including those employed by iOS and Android, have a notion of receiving input events and manipulating the user interface from a single thread. Users of those frameworks often use the terms "UI thread" and "main thread" interchangeably. Why impose this restriction? If you limit access to UI elements to a single thread, you simplify API usage and improve performance because you don't have to deal with locks and concurrency. Contrary to Munn's claims, Android does actually assign a higher scheduling priority to the UI thread than it does to background threads. I assume iOS does likewise.

Android and iOS are more alike than different in this respect. Either platform will hang and stop responding to user input if you monopolize the main thread with blocking operations (like file I/O) or slow drawing logic. This likely explains why Jason experiences jitters when visiting global search on iOS. On both platforms, programmers should perform all blocking operations on background threads and should never block the main thread.

As for the differences, iOS's Core Animation framework tweens animations in a background thread while Android does the same in the main thread. On iOS animations continue running, even when the application accidentally blocks the main thread. In addition, when scrolling, iOS's main thread goes into a "tracking" mode where it filters out certain types of events that might result in redrawing or other performance degradations. This has no affect on background operations. Nothing technically prevents Android from supporting these features, but their absence doesn't preclude smooth UIs on Android either. Hardware acceleration is far more important.

Does this mean we're going to start seeing iOS-quality user interfaces across the board on Android in the near future? No way. The reasons have little to nothing to do with bytecode or garbage collectors and everything to do with the community and tools. First, iOS app developers have far more experience taking advantage of hardware acceleration. They're experts in going out of the way to avoid software rendering. Hardware rendering requires a different mindset, and Android programmers will need time to catch up.

Second, Android programmers need to support both software and hardware rendering for awhile. This requires more code. Some Android devices support only software rendering while others, like the Xoom, actually require hardware rendering to achieve any semblance of smooth animation. Developing and maintaining smooth Android apps will require significantly more developer resources than accomplishing the same on iOS, at least until we can retire support for pre-Honeycomb devices. Programmers with limited resource will no doubt have to elide animations and settle for lowest common denominator solutions for awhile.

Third, from what I've seen, iOS developers have far better tools at their disposal. iOS developers can tweak and reload their applications in seconds, faster than you can reload a Ruby on Rails web page, while Android developers are lucky to do the same in tens of seconds. Being able to quickly iterate and tweak a UI is essential to achieving pixel-perfect, high frame rate animations. If Android programmers need to wait up to a minute after each tweak, they'll be at a significant disadvantage compared to their iOS counterparts. iOS provides amazing tools that overlay their UIs and help pinpoint and eliminate software rendering. In contrast, the UI performance and bugs in Android's emulator are so bad that developers resort to running on real devices only, even during development.

Android's training and legacy support issues will resolve themselves eventually. If I ran the Android team, I'd triple down on tool support. Android's emulator sounds great in theory, but it fails spectacularly in practice. Professional Android programmers simply can't and don't use it. Frankly, the emulation model is a lost cause. Android should replace it with an iOS-like simulator, one with tightly integrated profiling tools, capable of building and displaying apps in seconds. Ideally, the development build process would further cut down the turnaround time by eliding resource compilation, dex conversion, etc.

In the meantime, Android developers can achieve buttery user interfaces on Honeycomb and ICS devices, just like their iOS equivalents, by eliminating blocking operations from the main thread, minimizing time spent drawing in software, and allocating objects judiciously. Android programmers just have to work a little longer and harder to get there. Given our fierce dedication to a flawless user experience, my team plans to invest the necessary resources and accomplish just that. If you think you're up to the challenge, please apply.

For additional reading, Dianne has done a wonderful job explaining why the Android team made the choices they did and how those choices enabled some unique advantages over iOS.

8 Comments:

Blogger Unknown said...

Come on, I was a Software Engineer in Test. Not a testing intern :(

But, yeah, I agree, differences in app development mindset, tools, and difficulty is a big part of the problem.

3:29 PM  
Blogger Kartik Kumar said...

Can't argue on a technical issue. I would like to point out something.
Regarding this statement "Third, from what I've seen, iOS developers have far better tools at their disposable.", please substitute disposable for disposal.

Other than that, please blog more often.

10:25 PM  
Anonymous Anonymous said...

"The reasons have little to nothing to do with bytecode or garbage collectors and everything to do with the community and tools. First, iOS developers have far more experience taking advantage of hardware acceleration. They're experts in going out of the way to avoid software rendering."

This is so wrong. Except if you mean specifically Apple iOS developers, the rest of the world does not need special knowledge in order to achieve smooth ui experience. The framework does it for them automagically and that has everything to do with proper architecture and right choices on iOS behalf.

As for the bytecode, even though I can't prove it, I'd say that it has much to do with the current sad ui situation. I claim this taking evidence from JVM proper which has had decades of optimization effort, yet swing based applications still feels slow and laggy.

2:20 AM  
Blogger Bob said...

Koumparos, if you stick to the stock components, you probably don't have to think much about SW vs. HW acceleration, but it comes up quite a bit in highly customized UIs. See Square's signature screen and Card Case for examples. Android devs will need to learn all of these tricks and strategies.

From my experience, Swing apps typically feel slow because of I/O in the main thread. There's also the lack of HW acceleration. Not all Swing apps feel slow--see IntelliJ IDEA. In fact, one of my devs claims AppCode feels faster than XCode.

9:12 AM  
Blogger Michael B said...

Thanks bob, I was writing a reply to one of the initial posts and ended up deleting it because it became lengthy, sounded unnecessarily harsh(and I had difficulty rewording it) and didn't feel like the flames and trolls. Anyway it's good to see it corrected, although it will probably get referred to for the next couple of years.

Koumparos, also see Romain Guy's work before he joined Google - plenty of smooth swing examples there.

10:02 AM  
Blogger Sam said...

@Andrew Munn
To be fair to Bob, you do say you were an Intern on your LinkedIn page...

10:50 PM  
Blogger Cxnet said...

I agree with Bob here about the Emulator.
The only time I used it is for running samples when I first started a couple of years ago.
But I soon realized that it was not productive to use it and resorted to running my apps on the real device.

11:45 AM  
Blogger Jon Perlow said...

Bob,

This is a great post, but I thought I'd add some thoughts based on my experience working on Android at Beluga and Facebook.

1. GC is a huge performance problem for developing smooth android applications. At Facebook, one of the biggest performance problems we deal with is GCs pausing the UI thread. When dealing with lots of Bitmap data, GCs are frequent and hard to avoid. A single GC often results in dropped frames. Even if a GC only blocks the UI thread for a few milliseconds, it can significantly eat into the 16ms budget for rendering a frame.

2. The activity lifecycle makes developing smooth UI applications difficult. Since the OS controls the activity lifetime and all UI components use the Activity as its Context, it's nearly impossible to do caching of UI elements and UI elements are expensive to create. In Messenger, we found it very frustrating that we paid the cost of recreating the thread view every time a navigation from the thread list occurs despite the fact that the entire app is mostly switching between two activities. We ended up having to a large rearchitecture of the app to convert from Activities to Fragments and basically reinvent the wheel to achieve caching. On iOS, developer have much more control over the lifetime of their views and apps have a lot more memory to work with (multi-tasking on iOS takes a back seat to foreground app performance).

3. Many of us here are convinced there is just something fundamentally broken about kernel scheduling on android. We see cases where just submitting a task to a thread pool results in multi-millisecond pauses on the UI thread. These are cases where there is no real thread contention and no locks are being held. I have not done the necessary kernel debugging to figure out the cause, but it would appear that nobody has done the proper tuning and optimization of the kernel. I am sure that apple has gone to great lengths to ensure that they give the UI thread of the foreground app the highest priority at all costs (except perhaps the even higher priority core animation thread).

4. Do not underestimate the value of iOS's dedicated core animation thread. On Android, you have to be 100% perfect in order to not have dropped frames. This is extremely difficult in practice. It is very easy to introduce invalidations or relayouts that cause animations to drop frames. There are animations we've optimized that involved doing heroic work to ensure everything was pre-rendered before the animation starts. Furthermore, the APIs to force all the work to be done upfront are poorly documented and and the tools suck. If you have 10 things that you're doing wrong, you won't see an improvement until you've fixed all 10. If you fix 9 of them, the performance might still be as bad as if you fixed none of them. This makes debugging very frustrating. The dedicated animation thread on iOS is a huge win because you don't have to be perfect like you do on Android. iOS is designed to be fast (or at least faster) by default, whereas on Android, you have to be perfect.

5. Here's one of my favorite examples of how hard it is to do smooth animations on android (and this one is trivial on iOS). http://goo.gl/4oeMr The Android developers themselves added a default animation to lists in ICS that could only ever be smooth in a sample app that did nothing. Every built-in app on android, except the preferences app, overrode this animation. The preferences app didn't and it looked janky. There's just no way that an app can create a new activity in less than 16ms that renders anything real. They removed it in a dot-release of ICS because they realized it was impossible to make smooth. Why they shipped a default animation in the first place that they they then overrode in every app is beyond me. Again, on IOS, this type of animation is trivial because of the dedicated high-priority animation thread.

These are just a few examples. I could probably write a book on android performance at this point.

6:12 PM  

Post a Comment

<< Home