How Apps Use Reflection on Android?

This is my newsletter. I write about stuff I work on. Some issues will be very nerdy, some others very abstract. There is no form but substance.

You are receiving this mail, because we know each other and I value your opinion. However you can unsubscribe at the bottom of the mail.

//Introduction

We will discuss how some unicorns optimize their apps for better performance and why smaller companies can't yet do it.

//What is Reflection?

Reflection is the dynamic part of Java. It is said to be slow and cumbersome and to be avoided as much as possible (here, here and here for instance). Naturally, it is a extremely useful part of Java and in some cases impossible to do otherwise. In essence, it allows you to add some instructions at runtime.

//Reflection in Android Apps

To get a feel for the problem, I selected a few apps and sorted them by valuation in three groups: over 20B$, under 1B$ and in between.

For each, I counted the number of reflection calls and the total number of function calls. I then extracted the minimal lost time in seconds (see below).

A few remarks:

  • I used a small sample of apps. This is one limit of this analysis.

  • The "large cap" reflection usage is low (average of 0.6% of reflection of all function calls).

  • The "mid cap" (<1B$) usage is high (average of 8%).

  • The "small cap usage is low (average of 2%).

These large cap companies have made an effort to reduce the number of reflection calls per line of code. Why this focus on reflection? What benefits does it bring them?

//Reflection Overhead on Android

A common cliché among Android developers is that reflection slows down an app. Does it really? Let's quantify the reflection performance cost on Android using a real world app. I tested WeMoms, a French app connecting mothers with a few millions downloads using Android Studio Profiler running the app on an emulator running the latest Android version (API 28).

The screenshot below shows the reflection call getMethods executing in 10 ms.

Now we find another reflection calls: a newInstance. The two screenshots shows it takes 70 µs more than a direct calls.

These results looked so bad that I used another app and tested it using another profiler and I obtained similar results!

Also I looked at the Android source code to understand better and the reference resolution code explains why it is slow: Android iterates through the class hierarchy recursively. This scale roughly linearly with the number of classes in your app.

Eventually I built also another experiment using a JVM and a simple test program to confirm these results. The results were similar but the performance difference was in µs. I don't know if it's because the JVM performs better or because the test program is so simple that the performance overhead is small. If you have insight here, please reach out.

The take aways are:

  • Some reflection calls are horribly slow. They are the ones where you introspect and get a reference to some of your class. In a small app on Android, they need 5-20 ms per call (with profiler active).

  • Also calling a method using reflection instead of direct has a significant overhead. 70 µs more on a real Android app and probably more depending on the actual reflection call.

//Speedup

If all the code is run, the potential speedup in seconds is summarized in the table below. Also I added the speedup percentage in the worst case (i.e. all the reflection code is run on the main thread) assuming an average session (6 minutes)

Coinbase would be a whole 2 minutes faster and apps would be on average 12% faster. The "large cap" apps benefit a lot less (up to 32 s). They are already heavily optimized. Most small caps don't use reflection enough for the speedup to be significant.

//Conclusion

Currently only large caps companies are able to deal with reflection overhead. They all took actions to reduce it.. All companies would benefit from a better reflection system on Android. It would also help the Android ecosystem and the consumers because this would lever the playing field.

On a technical note, I wonder now what is the overhead of the JVM features invoke-dynamic and why these "small cap" companies are not using reflection as much.

As a final note, I really want to highlight that my sample size is really small and I should run the same experiment on all of the Play Store.

I am looking forward to your thoughts about all this.

//Further Readings