​inline​​ ,翻译过来为 内联 ,在 ​​Kotlin​​​ 中,一般建议用于 ​​高阶函数​​ 中,目的是用来弥补其运行时的 额外开销

其原理也比较简单,在调用时将我们的代码移动到调用处使用,从而降低方法调用时的 栈帧 层级。

栈帧: 指的是虚拟机在进行方法调用和方法执行时的数据结构,每一个栈帧里都包含了相应的数据,比如 局部参数,操作数栈等等。

Jvm在执行方法时,每执行一个方法会产生一个栈帧,随后将其保存到我们当前线程所对应的栈里,方法执行完毕时再将此方法出栈,

所以内联后就相当于省了一个栈帧调用。

如果上述描述中,你只记住了后半句,降低栈帧 ,那么此时你可能已经陷入了一个使用陷阱?

错误示例

如下截图中所示,我们随便创建了一个方法,并增加了 ​​inline​​ 关键字:

Kotlin内存陷阱 | inline虽好,但别滥用_高阶函数

观察截图会发现,此时IDE已经给出了提示,它建议你移除 ​​inline​​ , Why? 为什么呢?🥲

不是说内联可以提高性能吗,那么不应该任何方法都应该加 ​​inline​​ 提高性能吗?(就是这么倔强🤌🏼)

上面我们提到了,内联是会将代码移动到调用处,降低 一层栈帧,但这个性能提升真的大吗?

再仔细想想,移动到调用处,移动到调用处。这是什么概念呢?

假设我们某个方法里代码只有两行(我想不会有人会某个方法只有一行吧🥲),这个方法又被好几处调用,内联是提高了调用性能,毕竟节省了一次栈帧,再加上方法行数少。

但如果方法里代码有几十行?每次调用都会把代码内联过来,那调用处岂不💥,从而影响包大小。相比起来,此时内联的这点性能可以说完全不值得(因为虚拟机本身也会对方法进行优化)。

如下图所示,我们对上述示例做一个论证:

Kotlin内存陷阱 | inline虽好,但别滥用_代码移动_02

Jvm: 我谢谢你。

推荐示例

我们在文章最开始提到了,Kotlin ​​inline​​​ ,一般建议用于 ​​高阶函数(lambda)​​ 中。为什么呢?

如下示例:

Kotlin内存陷阱 | inline虽好,但别滥用_高阶函数_03

转成字节码后,可以发现,​​tryKtx()​​​ 被创建为了一个匿名内部类 ​​(Simple$test|1)​​ 。每次调用时,相当于需要创建匿名类的实例对象,从而导致二次调用的性能损耗。

那如果我们给其增加 ​​inline​​ 呢?🤖,反编译后相应的 java代码 如下:

Kotlin内存陷阱 | inline虽好,但别滥用_Kotlin_04

具体对比图如上所示,不难发现,我们的调用处已经被替换为原方法,相应的 ​​lambda​​ 也被消除了,从而显著减少了性能损耗。

总结

如果查看官方库相应的代码,如下所示,比如 ​​with​​ :

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return

不难发现,​​inline​​ 的大多数场景仅且在 高阶函数 并且 方法行数较短 时适用。因为对于普通方法,jvm本身对其就会进行优化,所以 ​​inline​​ 在普通方法上的的意义几乎聊胜于无。

总结如下:

  • 因为内联函数会将方法函数移动到调用处,会增加调用处的代码量,所以对于较长的方法应该避免使用
  • 内联函数应该用于使用了高阶函数(lambda)的方法,而不是普通方法。

关于本系列

​Kotlin​​ 是一个越用越爽的语言,从null安全,支持方法扩展与属性扩展,到内联方法、内联类,使用Kotlin也越来越变得简单舒心。但编程从来不是一件简单的事,那些看似简单的代码,底层往往隐藏着不容忽视的内存开销。本系列正是结合实际开发,分享日常经验,以此尽可能避免这些问题。追求 简、精、深,拒绝长篇废话。

更多文章如下:

  • Kotlin内存陷阱 | inline虽好,但别滥用
  • Kotlin内存陷阱 | 关于密封类,你可能还需要知道的
  • Kotlin内存陷阱 | 你的伴生对象可能真的不需要
  • Kotlin内存陷阱 | 注意,你的 lazy 可能没有意义
  • Kotlin内存陷阱 | 以下日常行为,请尽可能避免
  • ...