Posted by: Sam Lin, Product Manager, Android
This is the third in a series of blog posts by the Android Studio team diving into some of the details and behind the scenes of Project Marble. The following post was written by Sam Lin (product manager), Lingfeng Yang (tech lead), and Bo Hu (tech lead) on the Emulator team.
Today we are excited to give you an update on the progress we have made in the Android Emulator during Project Marble. One of the core goals we have is to make the Android Emulator the go-to device for app development. Physical Android devices are great, but we aim to add features and performance that make you even more efficient when developing and testing your Android apps.
We have heard that many app developers like the recent improvements we have made to the emulator, from 2 second start time, GPU graphics acceleration, to snapshots. However, we have also heard that the Android Emulator consumes too many system resources on your development computer. To address this issue, we created an effort in Project Marble to optimize the CPU usage for the Android Emulator. Without deviating from the original design principles, we’ve made significant improvements to the power efficiency and draw rate of the Android Emulator over the last few months during Project Marble. In this post, we will shed some light on the progress we release so far in Android Emulator 28.1 on the canary channel.
Preserving Design Principles While Reducing Overhead
The main benefit of the Android Emulator is to provide developers a scalable way to test the latest Android APIs across a variety of device configurations and screen resolutions, without buying physical devices for every configuration. As such, testing apps on Android Emulator should be as close as possible as testing on a physical device, while keeping the benefits of virtual devices.
In order to support the latest system images as soon as they are developed, we intentionally decided to design Android Emulator to be as close to a physical device as possible, as an emulator not a simulator. This approach ensures API correctness and high fidelity of Android system behaviors and interaction. When a new Android version comes out, we only need to ensure that our hardware abstraction layers (HALs) and kernel are compatible with the emulator and new system images, rather than have to re-implement all changes in the Android API for the new Android version from scratch ourselves. The net result of this architecture is that it greatly speeds up adoption of new system images for emulators.
However, such a full system emulation approach imposes overhead in both CPU cycles and memory access. In contrast, a simulator based approach would wrap analogous APIs on the host system with possibly less overhead. Therefore, our challenge is to preserve the accuracy and maintenance benefits of full system emulation while reducing the CPU and memory overhead.
Investigation into the Android Emulator Architecture
The Android Emulator runs the Android operating system in a virtual machine called an Android Virtual Device (AVD). The AVD contains the full Android software stack, and it runs as if it were on a physical device. The high-level architecture diagram is as follows.
Since the entire Android OS runs separately from the host OS, running the Android Emulator can cause background activity on the host’s machine even without any user input. After doing some technical investigation, the following tasks were some of the major consumers of CPU cycles when an AVD is idle:
- Google Play Store — app updates happen automatically when new versions are available.
- Background services — several on-demand services kept the CPU usage high when it assume the device was charging.
- Animations — such as Live wallpapers.
For these areas we did a deep set of technical investigations and landed on the following top five solutions to optimize the Android Emulator.
- Battery mode by default
- Emulator pause/resume
- Draw call overhead reduction
- macOS main loop IO overhead reduction
- Headless Build
Improvement #1 — Battery mode by default
Previously, the Android Emulator set the AVD’s battery charging mode to be on AC power. After thoughtful discussions and data analysis, we concluded that it is best to set an AVD on battery mode by default. This is because most of the Android framework, services and apps are optimized to save battery life, and these optimizations only kick in if the device (either physical or virtual) thinks it’s using the battery rather than charging off AC power.
However, it’s not enough to just default the AVD to use battery. That’s because being on battery mode also causes the screen to turn off automatically after a period of time. This can be confusing to users who are using the emulator on laptops or desktop, where there is an expectation that apps don’t randomly go to sleep and need to be woken up. To avoid this condition, Android Emulator will set screen off timeout using an ADB shell command to the maximum (~24 days) at each cold boot complete.
With these changes, Google Play Store will not update apps automatically on the battery mode, and avoid overloading the system. However, the auto-updating of apps can still be triggered by switching back to AC charging mode. This actually gives developers control on when to update apps automatically. Which can prevent interference in critical use cases, such as when the user simply wants to build and test a single app. The following chart compares CPU usage on battery versus on AC power:
Improvement #2 — Emulator pause/resume
In many cases, you may want an immediate guarantee that the emulator isn’t chewing up CPU cycles in the background during critical tasks such as the edit and build steps of the edit / build / deploy loop. To address this, we’re working on a console command and interface for pausing the emulator’s CPU usage completely. This can be accomplished by following console commands to pause/resume the AVD explicitly.
The challenge here is how to coordinate this Android Emulator state change with Android Studio. So when an app deploy happens, we auto resume the emulator. We are still working on this mechanism, and happy to hear your thoughts and feedback.
Improvement #3 — Draw call overhead reduction
We’ve also made changes in the Android Emulator engine that make it more efficient at drawing, which results in a smoother user experience when testing graphics-heavy apps with many objects on screen. For example, Emulator v28.1.10 draws 8% faster on GPU emulation stress test app compared with that in v28.0.23. We are also working on further optimizations in Android Q, and will share additional updates during the Android Q preview.
Improvement #4 — macOS main loop IO overhead reduction
A full system emulator must maintain some kind of method to notify the virtual OS that I/O operations on disk & network complete. Android Emulator is based on QEMU, and uses a main loop and iothreads to accomplish this. It has low overhead on Linux and Windows. However on macOS, we have seen higher CPU usage of the main loop due to its usage of the select() system call. Which is often not implemented efficiently. macOS does provide a low overhead method to wait on I/O: kqueue. We’ve found that the main I/O loop, that is currently based on select() can be replaced with a main I/O loop based on kqueue. This greatly decreased the CPU usage of the main loop, from ~10% to ~3%. Since this does not account for all idle CPU usage, the chart (below) does not show much change. Nevertheless, the difference is still observable.
Improvement #5 — Headless Build
For those of you using continuous integration systems for your Android app builds, we also worked on performance improvements in this area as well. By turning off the user interface in the Android Emulator, you can use access a new emulator-headless mode. This new mode runs tests in the background and uses less memory. It also takes about 100MB less, mainly because the Qt libraries we use for the user interface are not loaded. This is also a good choice to run automated tests when UI and user interactions are not required. The delta can be measured by starting 2 Emulator AVD instances as follows. Note that, the command line example specifies host GPU mode explicitly to ensure the comparison is under the same conditions.
To use the performance and resource optimization covered in this blog, download Android Emulator 28.1 available today on the canary channel. We are excited to share this early checkin-in on the progress with you, but we are definitely not done yet. We invite you to try the latest updates of Android Emulator today, and send us your feedback.