Kotlin 内联函数
为什么会出现内联函数?
因为lambda表达式会被正常编译成匿名类,这表示每使用一次lambda表达式,一个额外的类就会被创建。并且如果lambda捕捉了某个变量,那么每次调用的时候都会创建一个新的对象。这会带来运行时的额外开销,导致使用lambda比使用一个直接执行相同代码的函数效率更低。
如果使用inline修饰符标记一个函数,在函数被使用的时候编译器并不会生成函数调用代码,而是使用函数生成的真实代码替换每一次的函数调用。
内联函数的限制
不是所有使用lambda的函数都可以被内联。当函数被内联的时候,作为参数的lambda表达式的函数体 会被直击复制到最终生成的代码中。这将限制函数体中的对应的lambda参数的使用。如果lambda参数被调用,这样的代码能被容易的内联。但是如果lambda参数在某个地方被保存起来,以便后面可以继续使用,lambda表达式的代码将不能被内联,因为必须要有一个包含这些代码的对象存在。
参数一般直接调用或者作为参数传递给另一个inline函数,他是可以被内联的。否则,编辑器会禁止参数被内联并给出错误信息“Illegal usage of inline-paramter”(非法使用内联参数)。
何时将函数声明成内联
并不是所有的函数使用内联都可以提高性能的。使用inline关键字只能提高带有lambda参数函数的性能,其他情况需要额外的度量和研究。
对于普通函数调用,JVM已经提供了强大的内联支持。它会分析代码的执行,并在任何通过内联能够带来的好处时候将函数调用内联。这是在将字节码转换成机器代码时自动完成的。在字节码中,每一个函数的实现只会出现一次,并不需要跟Kotlin的内联函数一样,每个调用的地方都拷贝一次。再说,如果函数被直接调用,调用栈会更加清晰。
另一个方面,将带有lambda参数的函数内联能带来好处。首先,通过内联避免的运行时开销更加明显了。不仅节约了函数调用的开销,而且节约了为lambda创建匿名类,以及创建lambda实例对象的开销。其次,JVM目前并没有聪明到总是能将函数调用内联。最后,内联使得我们可以使用一些不可能被普通lambda使用的特性,比如非局部返回。
在使用inline的时候还需要注意代码长度。如果内联函数很大,将它的字节码拷贝到每一个调用点将会极大的增加字节码的长度。在这种情况下,你应该将那些与lambda参数无关的代码抽取到一个独立的非内联函数中。kotlin标准库中的内敛函数总是很小的。