Recently, in a client project, I was debugging a RecyclerView to diagnose poor scrolling behavior when I stumbled on something quite curious. I was using the on screen GPU rendering tool when I noticed that frames were being rendered even when I was not actively scrolling the RecyclerView. That is, I was not interacting with the screen whatsoever yet for some reason the GPU rendering bars were ticking away about every half second. Even more concerning, the rendering bars were well above the green line which meant that it took longer than 16 ms to render each frame, thus missing the 60 FPS target that all apps should shoot for.
At first I thought it was a rogue ProgressBar that was still rendering but hidden. This was unlikely given that a ProgressBar is a continuous animation which ideally will run at 60 FPS. I then enabled “Show GPU view updates” in the developer options which will “flash views inside windows when drawn with the GPU”. This helped me realize the culprit was right in front of me. It was a focused EditText with a blinking cursor ready to accept input. Each blink of the cursor coincided with a render bar being drawn. Here is an example:
I had discovered the cause of new frames being rendered, but now I needed to figure out why it was taking such a long time for each frame to be rendered. Surely a blinking cursor was not enough to push Android over the golden 60 FPS target. Furthermore, I noticed that as soon as I touched the screen, the frames would start rendering much faster, well below the 60 FPS green line. Here is an example:
Feel free to replicate this in any app with an EditText, you will experience the same behavior.
I shared my findings with a colleague at Raizlabs and he immediately referenced Project Butter. Project Butter was an undertaking by the Android team that started in Android 4.1 Jelly Bean in an effort to improve the touch latency and animation performance on Android devices. One of the ways this was achieved was by doing some clever CPU scaling which would increase the clock speed upon user input. This would ensure that whenever the user had a finger on the screen, the experience would be as smooth and as “buttery” as possible. A fun way to test this on your device is to download the system monitoring app CPU-Z and watch as the clock speed of your CPU cores increase when the screen is touched.
Android uses what is called an “Interactive” CPU governor which is “designed for latency-sensitive, interactive workloads.” A CPU governor is a set of rules that determine what clock speed the CPU should be at. There is an excellent explanation here in the Android source code of various types of governors, including the one Android uses. It makes sense to increase the CPU clock speed upon user input, but why when the user takes their finger off the screen does this cause the CPU clock speed to slow. This is the result of the CPU governor in Android saving battery by intelligently slowing the clock speed. That is exactly what is happening when we take our finger off the screen causing the cursor to take greater than 16 ms to render.
Since the user’s finger is not on the screen and the cursor blink is not what the CPU considers “high priority”, it can afford to downclock the CPU, causing the cursor to render a little slower. The slower rendering is imperceptible to the user, allowing the battery life of the device to be extended.
You may be wondering why downclocking the CPU has an effect on render performance if the GPU is responsible for handling all the drawing. The GPU is responsible for the drawing but it relies on the CPU to send it the necessary draw instructions. On Android, the CPU sends these draw commands through Open GL ES.
A great way to analyze the performance of your app is Systrace, which is provided in the Android SDK Tools. It records the various processes of your application for a specified amount of time, and then generates a HTML file with the results. Let’s use it to dig a little deeper into the rendering of our EditText cursor. Because we are concerned with the behavior of the CPU, I ran Systrace with the following advanced options: Power Management, CPU Frequency, CPU Idle, and CPU Load. I chose to record 5 seconds of activity. The first 2 seconds show the CPU in an idle state, with the last 3 seconds recording the processes while my finger was interacting with the screen. Feel free to view the trace here, but I will be showing screenshots of the parts necessary for our discussion.
Observe that there are four warning symbols that appear every half second during the first two seconds. Then the last three seconds shows that the system has detected our input. If you click on the warning, the message reads “Work to produce this frame was descheduled for several milliseconds, contributing to jank. Ensure that code on the UI thread doesn’t block on work being done on other threads”. Systrace is pointing out exactly what we observed in our GPU render bars. This warning, however, is a red herring. The user will not experience jank because they are not interacting with the screen. It would be nice if Systrace could differentiate between actual jank, and frames that took too long to render because the CPU governor downclocked the CPU. Also note that as soon as input is detected, the warnings disappear because the Interactive CPU governor has increased the clock speed.
Looking at the CPU section of the Systrace, we can see that the clock frequency increases for all of the CPUs as soon the system detects my finger has touched the screen. Remember, the brightened portion indicates when my finger was on the screen.
Toward the bottom of the Systrace there is a “Frames” section which indicates every time a frame has been drawn.
Observe that the drawing of frames is spaced out significantly for the first couple of seconds. But once I touch the screen, the system starts drawing frames much more quickly, at 60 FPS. I’m at a loss as to why all those frames are being rendered if the only change to the UI is still the cursor blink every half second. It is not because I have the “Show taps” developer option enabled, because the frames are still drawn at 60 FPS regardless of whether this is enabled. Have an idea why this is, please reach out in the comments!
If you enjoyed this post, you can follow me on Twitter @dickclucas, where I talk about Android, technology, and other related topics. Thanks for reading and please feel free to reach out in the comments section below or directly to our team at Raizlabs.