为什么Dagger的可重用范围比Singleton慢?

Pio*_*ski 3 performance kotlin dagger-2

我一直认为,在使用Dagger2时,我们应该使用@Reusable范围,而不是@Singleton如果我们不需要保证总是获得相同的实例,因为@Singleton使用了双重检查,这既昂贵又缓慢。

但是,我进行了简单的性能测试,结果如下:

Reusable  4474 ms
Singleton 3603 ms
Run Code Online (Sandbox Code Playgroud)

这是代码:

@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)
    }
}
Run Code Online (Sandbox Code Playgroud)

在构造较重的类(我尝试过使用Retrofit)和带@Provide注释的方法而不是构造函数注入时,存在相同的现象。

我在测试中犯了错还是@Reusable慢了?如果是这样,我们应该在哪里使用它?有什么好处@Singleton吗?

Jef*_*ica 5

正如David Medenjak在评论中提到并链接的那样,JVM中的微基准测试很难正确。即使将您的结果看成是面值,在一个十亿次调用的内部循环中,这些调用的平均结果平均在1ns之内,彼此之间的比例为20%。

尽管我已经写了一个单独的SO答案,其中包含更多详细信息,但我可以解决您的“它有没有好处”的问题:

  1. 主要的性能优势@Reusable在多线程应用程序中进行构造时,因为在竞争情况下,可能@Reusable会为单独的线程创建单独的对象,而不是同步创建。支付了创建成本后(就像您在每个块的第一次调用中所做的那样),接下来的十亿个调用是免费的(或接近免费),尤其是使用JVM内联和缓存时,并且在同一线程的同一堆栈中。尽管您的基准测试没有揭示它,但是如果绑定的创建有任何线程争用,您仍然可能会看到更好的性能。@Reusable

  2. 内存的主要优势@Reusable是可重用实例保存在直接使用它的最窄组件中。如果您有一个Android Fragment组件作为@Reusable绑定的唯一使用者,那么当您销毁Fragment并收集其Component时,Android将释放并回收该内存。

  3. Dagger的主要可用性优势@Reusable在于,与@Singleton自定义范围不同,@Reusable绑定可以包含在任何组件中,而不管该组件上有多少个范围注释。如果您有@Singleton绑定,则绝对需要将绑定安装在带有注释的组件中@Singleton

  4. 主要开发人员使用的优势@Reusable在于,不像注释绑定@Singleton或者@ActivityScoped,您声明结合是无状态的或以其他方式必须是@Singleton。如果要在Dagger之外使用绑定(或者有一天要替换Dagger),则需要记录或确定@Singleton绑定是否必要,@Singleton或者仅仅是一个优化机会。随着@Reusable这种歧义消失。