Kotlin进阶知识(七)——内联函数:消除lambda带来的运行时开销

引用:使用**inline**修饰符标记一个函数,在函数被使用的时候编译器并不会生成函数调用的代码markdown

1、内联函数如何运行

当一个函数被声明为inline时,它的函数体是内联的——换句话说,函数体会被直接替换到函数被调用的地方,而不是被正常调用。函数

inline fun <T> synchronized(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
        return action()
    }
    finally {
        lock.unlock()
    }
}

fun foo(l: Lock) {
    println("Before sync")

    synchronized(l) {
        println("Action")
    }

    println("After sync")
}
复制代码

上述代码在转换成Java代码时,inline修饰的方法会自动添加到对应的方法中性能

public static final void foo(@NotNull Lock l) {
      String var1 = "Before sync";
      System.out.println(var1);
      l.lock();

      try {
         String var3 = "Action";
         System.out.println(var3);
         Unit var8 = Unit.INSTANCE;
      } finally {
         l.unlock();
      }

      var1 = "After sync";
      System.out.println(var1);
   }
复制代码

注意:lambda表达式和synchronized函数的实现都被内联了。由lambda生成的字节码称为了函数调用者定义的一部分,而不是被包含在一个实现了函数接口的匿名类中。ui

在调用内联函数的时候也能够传递函数类型的变量做为参数:spa

class LockOwner(val lock: Lock) {
    fun runUnderLock(body: () -> Unit) {
        // 传递一个函数类型的变量做为参数,而不是一个lambda
        synchronized(lock, body)
    }
}
复制代码

这种状况下,lambda的代码在内联函数被调用点是不可用的,所以并不会被内联。 只有synchronized的函数体别内联了,lambda才会被正常调用。code

若是在两个不一样的位置使用同一个内联函数,可是用的是不一样的lambda,那么内联函数会在每个被调用的位置被分别内联。orm

内联函数的代码会被拷贝到使用它的两个不一样为孩子,并把不一样的lambda替换到其中。对象

2、内联集合操做

大部分标准库中的集合函数都带有lambda参数。相比于使用标准库函数,直接实现这些操做不是更高效吗?接口

  • 使用lambda过滤一个集合
data class PersonNew(val name: String = "", val age: Int = 0)

val people = listOf(PersonNew("Alice", 29), PersonNew("Bob", 31))

fun filterSetByLambdaTest() {
    println(people.filter { it.age < 30 })
}

// 输出结果
[Person(name=Alice, age=29)]
复制代码
  • 手动过滤一个集合
fun filterSetByManualTest() {
    val result = mutableListOf<PersonNew>()
    for (person in people) {
        if (person.age < 30) result.add(person)
    }
    println(result)
}
复制代码

在Kotlin中,filter函数被声明为内联函数。这意味着filter函数,以及传递给它的lambda的字节码会被一块儿关联到filter被调用的地方。最终,第一种实现所产生的字节码和第二种实现所产生的字节码大体同样的。编译器

于是Kotlin对内联函数的支持让你没必要担忧性能问题

注意: 一、有大量元素须要处理,中间集合的运行开销将成为不可忽视的问题,于是使用asSequence调用,用序列来替代集合。 二、只在处理大量数据的集合时序列有用,小集合能够用普通的集合操做处理便可。

3、决定什么时候将函数声明成内联

使用inline关键字只能提升带有lambda参数的函数的性能,其余状况须要额外的度量和研究。

对于普通的函数调用JVM已经提升了强大的内联支持。它会分析代码的执行,并在任何经过内联可以带来好处的时候将函数调用内联。

lambda参数的函数内联能带来的好处

  • 经过内联避免的运行时开销更下明显了。不只节约了函数调用的开销,并且节约了为lambda建立匿名类,以及建立lambda实例对象的开销。
  • JVM目前并无老是将函数调用内联。
  • 内联可使用一些不可能被普通lambda使用的特性,好比非局部返回。
相关文章
相关标签/搜索