Kotlin内存陷阱inline使用技巧示例详解
作者:Petterp
引言
inline
,翻译过来为 内联 ,在 Kotlin
中,一般建议用于 高阶函数
中,目的是用来弥补其运行时的 额外开销。
其原理也比较简单,在调用时将我们的代码移动到调用处使用,从而降低方法调用时的 栈帧 层级。
栈帧: 指的是虚拟机在进行方法调用和方法执行时的数据结构,每一个栈帧里都包含了相应的数据,比如 局部参数,操作数栈等等。
Jvm在执行方法时,每执行一个方法会产生一个栈帧,随后将其保存到我们当前线程所对应的栈里,方法执行完毕时再将此方法出栈,
所以内联后就相当于省了一个栈帧调用。
如果上述描述中,你只记住了后半句,降低栈帧 ,那么此时你可能已经陷入了一个使用陷阱?
错误示例
如下截图中所示,我们随便创建了一个方法,并增加了 inline
关键字:
观察截图会发现,此时IDE已经给出了提示,它建议你移除 inline
, Why? 为什么呢?🥲
不是说内联可以提高性能吗,那么不应该任何方法都应该加 inline
提高性能吗?(就是这么倔强🤌🏼)
上面我们提到了,内联是会将代码移动到调用处,降低 一层栈帧,但这个性能提升真的大吗?
再仔细想想,移动到调用处,移动到调用处。这是什么概念呢?
假设我们某个方法里代码只有两行(我想不会有人会某个方法只有一行吧🥲),这个方法又被好几处调用,内联是提高了调用性能,毕竟节省了一次栈帧,再加上方法行数少(暂时抛弃虚拟机优化这个底层条件)。
但如果方法里代码有几十行?每次调用都会把代码内联过来,那调用处岂不💥,带来的包大小影响某种程度上要比内联成本更高😵💫!
如下图所示,我们对上述示例做一个论证:
Jvm: 我谢谢你。
推荐示例
我们在文章最开始提到了,Kotlin inline
,一般建议用于 高阶函数(lambda) 中。为什么呢?
如下示例:
转成字节码后,可以发现,tryKtx()
被创建为了一个匿名内部类 (Simple$test|1)
。每次调用时,相当于需要创建匿名类的实例对象,从而导致二次调用的性能损耗。
那如果我们给其增加 inline
呢?🤖,反编译后相应的 java代码 如下:
具体对比图如上所示,不难发现,我们的调用处已经被替换为原方法,相应的 lambda
也被消除了,从而显著减少了性能损耗。
小结
如果查看官方库相应的代码,如下所示,比如 with
:
public inline fun <T, R> with(receiver: T, block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return receiver.block() }
不难发现,inline
的大多数场景仅且在 高阶函数 并且 方法行数较短 时适用。因为对于普通方法,jvm本身对其就会进行优化,所以 inline
在普通方法上的的意义几乎聊胜于无。
总结
- 因为内联函数会将方法函数移动到调用处,会增加调用处的代码量,所以对于较长的方法应该避免使用;
- 内联函数应该用于使用了 高阶函数(lambda) 的方法,而不是普通方法。
以上就是Kotlin内存陷阱inline使用技巧示例详解的详细内容,更多关于Kotlin内存陷阱inline使用的资料请关注脚本之家其它相关文章!