5

I've always thought that, when working with Dagger2, we should use @Reusable scope instead of @Singleton if we don't need the guarantee to get always the same instance, since @Singleton used double checking, which is expensive and slow...

However, I've made a simple performance tests and here are the results:

Reusable  4474 ms
Singleton 3603 ms

Here is the code:

@Singleton
@Component
interface AppComponent {

    fun getReusable(): ReusableFoo

    fun getSingleton(): SingletonFoo
}

@Reusable
class ReusableFoo @Inject constructor()

@Singleton
class SingletonFoo @Inject constructor()

class TestClass {
    @Test
    fun test() {
        val component = DaggerAppComponent.builder().build()
        measure {
            component.getReusable()
        }
        measure {
            component.getSingleton()
        }
    }

    private fun measure(block: () -> Unit) {
        val start = System.currentTimeMillis()
        (0..1000000000).forEach { block() }
        println(System.currentTimeMillis() - start)
    }
}

The same phenomenon when constructing heavier class (I've tried with Retrofit) and with @Provide annotated methods instead of constructor injection.

Did I make a mistake in my test or simply @Reusable is slower? If so, where should we use it? Does it have any benefits over @Singleton?

  • 2
    It is actually not that easy to construct a scenario where the DCL idiom is significantly more expensive than plain, non-threadsafe lazy init idiom. The only difference is that with DCL you always have to fetch from "memory", but in a tight loop you're just reaching to the CPU cache. Outside a tight loop, both idioms will probably need memory access and are on the same footing. – Marko Topolnik Jan 10 '18 at 11:49
  • 4
    Another point: you have measured 3.6 nanoseconds for singleton and 4.4 nanoseconds for reusable. That's a difference of 0.8 nanoseconds, i bet there are lots and lots of candidate micro-level explanations for it, all of them quite unenlightening. Your real conclusion should be "it makes no difference at all" which scope you use. Or you should come up with a different benchmark that would show a more pronounced difference. – Marko Topolnik Jan 10 '18 at 12:05
  • 3
    Try flipping the order of what you test first and I'd not be surprised if it suddenly stated the opposite, also possibly see here about micro benchmarks: https://stackoverflow.com/a/513259/1837367 – David Medenjak Jan 10 '18 at 14:25
  • @DavidMedenjak I've tried it ;) But there was no difference. Thanks for this link - the response is very valuable, especially one comment below it: *Also, never use System.currentTimeMillis() unless you are OK with + or - 15 ms accuracy* – Piotr Aleksander Chmielowski Jan 10 '18 at 19:18

1 Answers1

7

As David Medenjak mentioned and linked in the comments, micro benchmarks in the JVM are difficult to get right. Even taking your results at face value, the calls average out to within 1ns and 20% of each other in a tight billion-call inner loop.

Though I've written a separate SO answer with more details, I can address your question of "does it have any benefits":

  1. The main performance advantage to @Reusable is during construction in a multi-threaded app, because in the event of a race condition @Reusable will potentially create a separate object for a separate thread rather than synchronizing the creation. Once you've paid the creation cost (as you do in the first call on each block), the next billion calls are free (or close to free), especially with JVM inlining and caching, and within the same stack in the same thread. Though your benchmark doesn't reveal it, you might still see better performance with @Reusable if there is any thread contention for your binding's creation.

  2. The main memory advantage to @Reusable is that the reusable instance is kept in the narrowest component that uses it directly. If you have an Android Fragment component as your only consumer of a @Reusable binding, Android will release and reclaim that memory when you destroy the Fragment and collect its Component.

  3. The main Dagger usability advantage to @Reusable is that, unlike @Singleton and custom scopes, @Reusable bindings can be included in any component regardless of how many scope annotations are on the component. If you have a @Singleton binding you are absolutely required to install the binding in a component annotated with @Singleton.

  4. The main developer usability advantage to @Reusable is that, unlike bindings annotated with @Singleton or @ActivityScoped, you are declaring that the binding is not stateful or otherwise required to be @Singleton. If you want to use your binding outside of Dagger (or you want to replace Dagger someday), you'll need to document or determine whether a @Singleton binding is necessarily @Singleton or whether it's simply an optimization opportunity. With @Reusable that ambiguity disappears.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251