【问题标题】:android kotlin - are Function References slow due to reflection?android kotlin - 函数引用是否由于反射而变慢?
【发布时间】:2020-07-25 15:40:10
【问题描述】:

Reflection 被认为在 android 上有点耗时。所以我想知道这样的函数引用:

fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))

::isOdd调用是不必要的负担?

不使用它们会更有效吗?

更新:做一些轻量级的指标我做了以下:

    class MainActivity : Activity() {

    val numbers = listOf(1, 2, 3)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        doRegular()
        doReflection()
    }

    fun isOdd(x: Int) = x % 2 != 0

    fun doRegular() {
        val start = System.currentTimeMillis()
        for (i in 0..999999) {
            numbers.filter { it % 2 != 0 }
        }
        Log.v("myTag", "regular: +${System.currentTimeMillis() - start}")
    }

    fun doReflection() {
        val start = System.currentTimeMillis()
        for (i in 0..999999) {
            numbers.filter(::isOdd)
        }
        Log.v("myTag", "reflection: ${System.currentTimeMillis() - start}")
    }
}

打印语句结果为:

//*****this was the first attempt before compiler could optimise anything
        regular: +577     
        reflection: 318
  //*********
  
    regular: +376
    reflection: 327
    
    regular: +593
     reflection: 343
    
     regular: +417
     reflection: 251
     
     regular: +243
     reflection: 238
     
     regular: +241
     reflection: 238
     
     regular: +238
     reflection: 237
     
     regular: +235
     reflection: 247
     
     regular: +237
     reflection: 247
     
     regular: +230
     reflection: 268

根据这些结果,您会得出什么结论?

更新:有些人问我为什么认为它使用反射。它基于此:

这个stackoverflow answer 似乎说明了它的反射:official doc 的标题以反射作为主要标题:因此我很困惑。

【问题讨论】:

  • 是什么让你认为::isOdd 在使用反射?
  • 作为基准的经验法则,您将其运行 5 倍或更多,并丢弃大的差异。你跑了多少次才得到这样的数字?同样重要的是要知道 jvm 是新鲜的还是已经运行了此代码并有机会通过一些 jit 对其进行优化。
  • @Slaw 我在底部的问题中发表了评论,它基于两个链接
  • 也许当 Kotlin 以 JVM 以外的平台为目标时,它真的在后台使用反射?即使函数不是内联,我也希望,当 JVM 是目标平台时,函数引用的工作方式类似于 Java 中的方法引用——不是通过反射。
  • 看到最后的迭代收敛到稳定的数字非常有趣。这里可能发生的是常规进行某种优化。如果在 intellij 上尝试将字节码编译为 Java,它可能会为您提供答案。在某些情况下,制作 isodd inline 可能会更快。此外,对于某些基准测试而言,编译器太聪明了,无法按照我们的想法去做。

标签: android kotlin android-reflection


【解决方案1】:

::isOdd 肯定是用来引用函数的,但除非确实需要使用“反射”,否则不使用它(反射)。类似于在字节码中将 Int 更改为 int,除非我们需要存储引用,如果我们创建集合,则只有它表示为 java.lang.Integer

Kotlin 编译器很聪明,并且在幕后工作。

/**
 * Returns a list containing only elements matching the given [predicate].
 * 
 * @sample samples.collections.Collections.Filtering.filter
 */
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

过滤器函数是直接在调用点嵌入 lambda/引用而不实际使用反射的内联函数。

如果您看到 JVM 字节码,您将看到以下反编译的 Java 代码:

// of numbers.filter { it % 2 != 0 }
while(var6.hasNext()) {
    Object element$iv$iv = var6.next();
    int it = ((Number)element$iv$iv).intValue();
    int var9 = false;
    if (it % 2 != 0) {              // <- check this out
        destination$iv$iv.add(element$iv$iv);
    }
}

// of numbers.filter(::isOdd)
while(var6.hasNext()) {
    Object element$iv$iv = var6.next();
    int p1 = ((Number)element$iv$iv).intValue();
    int var9 = false;
    if (isOdd(p1)) {                  // <- check this out
        destination$iv$iv.add(element$iv$iv);
    }
}

实际上这里不涉及反射。

旁注:我在 here 上对 Kotlinlang 官方 slack 有过类似的问题,我得到了很多关注,但只有 1 条回复是仅关于 here in softwareengineering stackexchange subdomain 所述的元加工。

这是一个 ss:

【讨论】:

  • 我认为即使您以不同于 filter 的方式使用,您也会得到类似的结果。
  • @Fabio 是的,任何内联函数都有隐式内联 lambda,它可以在编译时解决问题 :)
【解决方案2】:

函数引用不使用反射,它们就像访问一个字段,但用于函数。

因此,这些引用在编译时解决。如果您犯了错误,例如拼写错误的函数名称,它将无法编译。查找和调用同一个拼写错误的函数的反射在运行时会失败。

【讨论】:

  • 但是这个 stackoverflow 的答案似乎说明了它的反射:stackoverflow.com/a/52463397/835883,官方文档的标题以反射为主要标题:kotlinlang.org/docs/reference/reflection.html 因此我很困惑。
  • 这是一个很好的观点,我需要阅读更多内容。我的看法是您的代码将其视为 lambda。如果您希望将其视为反射,则其语法相同,但用途不同:例如::odd.name。无论如何,我需要花些时间看看它,以确保我知道我在说什么。
【解决方案3】:

我想说解决这个问题的最好方法是计时!不过,我预计反射会更耗时。

val numbers = listOf(1, 2, 3)

fun isOdd(x: Int) = x % 2 != 0

fun doRegular() {
    val start = System.currentTimeMillis()
    for (i in 0..999999) {
        numbers.filter { it % 2 != 0 }
    }
    println(System.currentTimeMillis() - start)
}

fun doReflection() {
    val start = System.currentTimeMillis()
    for (i in 0..999999) {
        numbers.filter(::isOdd)
    }
    println(System.currentTimeMillis() - start)
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 2010-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多