Comparing the Performance of Dependency Injection Libraries
The dependency injection (DI) has become an increasingly popular tool in Android development, and for good reason. Injections reduce the amount you have to code (and hence, debug), facilitating the creation of better apps and a smoother development process. While it may be tempting to toss in dependencies to a variety of libraries, it’s also important to keep in mind the potential toll that dependency injections can have on your application’s performance. To help you navigate through the pros and cons of dependencies, we’re going to compare three of the most popular libraries from a performance point of view.
We’ll compare three dependency injection libraries:
Roboguice and Dagger work in different ways. Roboguice injects code at runtime using reflection, and Dagger does so using mostly Java compile-time annotation processors. Of course, this variation has a huge effect on an application’s performance because reflection overhead is more noticeable in Android. It’s often tempting to opt for the easier to use Roboguice, despite its drawbacks in performance. Yet this is precisely the kind of thinking that should be avoided - the extra effort is more than worth the increased performance. (Note that we don’t discuss Butter Knife here because it does view binding, not general dependency injection. Of course, if you only want to do view binding, Butter Knife is the library to use.)
In the remaining of this post, we first present performance results on micro-benchmarks, then show results on some real-world apps with millions of downloads, and lastly compare the method counts in the three DI libraries.
We’ve created a github repository with three demo apps. Take a moment to check it out - it should be pretty easy to understand what’s going on if you’ve used any of these libraries before. All these apps have the same graph, and we inject dummy objects to calculate the DI overhead. We also output some logs in order to confirm that the injections are successful.
In total we do 5455 injections:
- 4800 model classes (A* .. F*) into Test*
- 600 Test* classes into TestManager*
- 40 Test* classes into MainActivity
- 15 TestManager clases into MainActivity
(Library specific features, like views injection in Roboguice, aren’t used).
For time measurements we’ll use Nimble.App. Here are results on the same dummy app, using the three different libraries we mentioned earlier:
- Roboguice micro-benchmark results, 4 seconds startup time
- Dagger1 micro-benchmark results, 1.1 seconds startup time
- Dagger2 micro-benchmark results, 1 second startup time
All this data can be kind of confusing, so we’ll walk you through it.
By default, NimbleDroid will display the general cold startup time of the app. We can check out the performance of specific methods by navigating to Analysis Details -> Icicle Graph. Here, you should see something like this:
Surprisingly, Roboguice takes an entire 3923ms to inject! That’s a tremendous overhead. The problem is further exacerbated when we take into account the fact that this injection is only used in conjunction with dummy objects - in a real application, injected objects will initialize other objects, and this in turn will require additional time to process. It’s important to realize that this injection is simply not worth it. After all, the recommended cold startup time is 2 seconds, but we’ve already spent 4 seconds just on dependency injections.
Ok, now let’s compare this with Dagger 1 and Dagger 2 results.
As you can see , Roboguice is 25 times slower then Dagger 1. Dagger 2 is slightly faster than Dagger 1, taking 64ms to inject as opposed to Dagger 1’s 154ms.
Now let’s examine some actual apps. One of the nicest examples for this is Skype.
In older versions, Skype used Roboguice for injections:
As you can see, it takes 2415ms for injection when they used Roboguice.
Over time, however, they switched to Dagger, reducing injection time to a bare 170ms:
Here are some popular apps with similar problems:
- Groupon - Shop Deals & Coupons (~700ms overhead)
- ScanLife Barcode & QR Reader (~1,200ms overhead)
- Amex Mobile (~1,700ms overhead), and the latest new version (~274ms) switches to Dagger.
- Fandango Movies (~3,400ms overhead)
One More Thing: Method Count
There’s also one more significant drawback to using Roboguice. Going to the App Info of our dummy Roboguice app, we see this:
Under the “Method Count” section, NimbleDroid tells us that Roboguice demo app has 33,189 methods.
Now let’s look at Dagger 1:
and Dagger 2:
33200 methods vs 24000 methods! Android has a 65k method limit for single dex files, so ~10000 methods can make a huge difference. Using Roboguice also adds about 300kb to the final APK size.
Note: we didn’t use proguard for these tests, as we wanted to check library overheads without post optimizations.
Recommendation: Be extra careful when using Roboguice in your Android apps because it has bigger performance overhead and method count than Dagger libraries. Use Dagger 2 if you can because Dagger 2 is slightly better than Dagger 1.