inline
,翻译过来为 内联 ,在 Kotlin
中,一般建议用于 高阶函数
中,目的是用来弥补其运行时的 额外开销。
其原理也比较简单,在调用时将我们的代码移动到调用处使用,从而降低方法调用时的 栈帧 层级。
栈帧: 指的是虚拟机在进行方法调用和方法执行时的数据结构,每一个栈帧里都包含了相应的数据,比如 局部参数,操作数栈等等。
Jvm在执行方法时,每执行一个方法会产生一个栈帧,随后将其保存到我们当前线程所对应的栈里,方法执行完毕时再将此方法出栈,
所以内联后就相当于省了一个栈帧调用。
如果上述描述中,你只记住了后半句,降低栈帧 ,那么此时你可能已经陷入了一个使用陷阱?
错误示例
如下截图中所示,我们随便创建了一个方法,并增加了 inline
关键字:
观察截图会发现,此时IDE已经给出了提示,它建议你移除 inline
, Why? 为什么呢?🥲
不是说内联可以提高性能吗,那么不应该任何方法都应该加
inline
提高性能吗?(就是这么倔强🤌🏼)
上面我们提到了,内联是会将代码移动到调用处,降低 一层栈帧,但这个性能提升真的大吗?
再仔细想想,移动到调用处,移动到调用处。这是什么概念呢?
假设我们某个方法里代码只有两行(我想不会有人会某个方法只有一行吧🥲),这个方法又被好几处调用,内联是提高了调用性能,毕竟节省了一次栈帧,再加上方法行数少。
但如果方法里代码有几十行?每次调用都会把代码内联过来,那调用处岂不💥,从而影响包大小。相比起来,此时内联的这点性能可以说完全不值得(因为虚拟机本身也会对方法进行优化)。
如下图所示,我们对上述示例做一个论证:
Jvm: 我谢谢你。
推荐示例
我们在文章最开始提到了,Kotlin inline
,一般建议用于 高阶函数(lambda)
中。为什么呢?
如下示例:
转成字节码后,可以发现,tryKtx()
被创建为了一个匿名内部类 (Simple$test|1)
。每次调用时,相当于需要创建匿名类的实例对象,从而导致二次调用的性能损耗。
那如果我们给其增加 inline
呢?🤖,反编译后相应的 java代码 如下:
具体对比图如上所示,不难发现,我们的调用处已经被替换为原方法,相应的 lambda
也被消除了,从而显著减少了性能损耗。
总结
如果查看官方库相应的代码,如下所示,比如 with
:
不难发现,inline
的大多数场景仅且在 高阶函数 并且 方法行数较短 时适用。因为对于普通方法,jvm本身对其就会进行优化,所以 inline
在普通方法上的的意义几乎聊胜于无。
总结如下:
- 因为内联函数会将方法函数移动到调用处,会增加调用处的代码量,所以对于较长的方法应该避免使用;
- 内联函数应该用于使用了高阶函数(lambda)的方法,而不是普通方法。
关于本系列
Kotlin
是一个越用越爽的语言,从null安全,支持方法扩展与属性扩展,到内联方法、内联类,使用Kotlin也越来越变得简单舒心。但编程从来不是一件简单的事,那些看似简单的代码,底层往往隐藏着不容忽视的内存开销。本系列正是结合实际开发,分享日常经验,以此尽可能避免这些问题。追求 简、精、深,拒绝长篇废话。
更多文章如下:
- Kotlin内存陷阱 | inline虽好,但别滥用
- Kotlin内存陷阱 | 关于密封类,你可能还需要知道的
- Kotlin内存陷阱 | 你的伴生对象可能真的不需要
- Kotlin内存陷阱 | 注意,你的 lazy 可能没有意义
- Kotlin内存陷阱 | 以下日常行为,请尽可能避免
- ...