Kotlin 朱涛-8 实战 inline 函数式编程 词频统计
目录
08 | 实战:用Kotlin写一个英语词频统计程序
高阶函数的实现原理
fun foo(block: () -> Unit) {
block()
}
fun main() {
var i = 0
foo { i++ }
}
反编译成 Java 后:
public final class HigherOrderExampleKt {
public static final void foo(Function0 block) { // 参数是一个接口
block.invoke();
}
public static final void main() {
int i = 0;
foo((Function0) (new Function0() { // 参数是一个实现 Function0 接口的【匿名内部类】
public final void invoke() { // 接口中定义的方法,这个方法【没有参数】
i++;
}
}));
}
}
可以看到,Kotlin 高阶函数当中的函数类型参数,变成了 Function0,而 main() 函数当中的高阶函数调用,也变成了匿名内部类的调用方式。
Function0 是 Kotlin 标准库当中定义的接口,它代表没有参数的函数类型。Kotlin 一共定义了 23 个类似的接口,从 Function0 一直到 Function22,分别代表了无参数的函数类型到22 个参数的函数类型。
public interface Function0<out R> : Function<R> {
public operator fun invoke(): R
}
inline 的实现原理
使用 inline 优化过的高阶函数:
inline fun foo(block: () -> Unit) { // 唯一的区别:多了一个关键字 inline
block()
}
fun main() {
var i = 0
foo { i++ }
}
反编译后的 Java 后:
public final class HigherOrderInlineExampleKt {
public static final void foo(Function0 block) { // 没有变化
block.invoke();
}
public static final void main() {
int i = 0; // 区别:匿名内部类不见了,方法调用也不见了
int i = i + 1; // 原理:将 inline 函数当中的代码拷贝到调用处
}
}

inline 的作用其实就是:将 inline 函数当中的代码拷贝到调用处。
inline 性能
不使用 inline 时:
main()中需要调用foo(),多了一次函数调用的开销foo()中需要创建了匿名内部类对象,这也是额外的开销
使用 JMH 测试 inline 性能
为了验证使用 inline 前后的性能差异,我们可以使用 JMH(Java Microbenchmark Harness)对这两组代码进行性能测试。JMH 可以最大程度地排除外界因素的干扰(比如内存抖动、虚拟机预热),从而判断出我们这两组代码执行效率的差异。它的结果不一定非常精确,但足以说明一些问题。
下面以两组测试代码为例,来探究下 inline 到底能为我们带来多少性能上的提升:
fun foo(block: () -> Unit) = block() // 不用 inline 的高阶函数
inline fun fooInline(block: () -> Unit) = block() // 使用 inline 的高阶函数
// 测试无 inline 的代码
@Benchmark
fun testNonInlined() {
var i = 0
foo { i++ }
}
// 测试有 inline 的代码
@Benchmark
fun testInlined() {
var i = 0
fooInline { i++ }
}
最终的测试结果如下,分数越高性能越好:
Benchmark Mode Score Error Units
testInlined thrpt 3272062.466 ± 67403.033 ops/ms
testNonInlined thrpt 355450.945 ± 12647.220 ops/ms
从上面的测试结果我们能看出来,是否使用 inline 的效率相差 10 倍。
测试多层嵌套的性能差异
为了模拟复杂的代码结构,我们可以简单地将上面这两个函数分别嵌套 10 个层级,然后看看它们之间的性能差异:
@Benchmark
fun testNonInlined() {
var i = 0
foo {
foo {
foo {
foo {
foo {
foo {
foo {
foo {
foo {
foo {
i++
}
}
}
}
}
}
}
}
}
}
}
@Benchmark
fun testInlined() {
var i = 0
fooInline {
fooInline {
fooInline {
fooInline {
fooInline {
fooInline {
fooInline {
fooInline {
fooInline {
fooInline {
i++
}
}
}
}
}
}
}
}
}
}
}
Benchmark Mode Score Error Units
testInlined thrpt 3266143.092 ± 85861.453 ops/ms
testNonInlined thrpt 31404.262 ± 804.615 ops/ms
可以看到,在嵌套了 10 个层级以后,testInlined 的性能几乎没有什么变化;而 testNonInlined 的性能比 1 层嵌套差了 10 倍。
在这种情况下,testInlined() 与 testNonInlined() 之间的性能差异就达到了 100 倍。
如果反编译成 Java 代码,能看到:
- 对于 testNonInlined(),由于 foo() 嵌套了 10 层,它反编译后的代码也嵌套了 10 层函数调用,中间还伴随了 10 次匿名内部类的创建
- 而对于 testInlined(),则只有简单的两行代码,完全没有任何嵌套的痕迹
inline 的使用限制
Kotlin 官方建议:仅将 inline 用于修饰高阶函数。而且,也不是所有高阶函数都可以用 inline
IntelliJ IDEA 会对使用 inline 修饰的普通函数发出警告:
Expected 期望 performance impact 性能影响 of inlining is insignificant 微不足道. Inlining works best for functions with parameters of
functional types函数类型参数的函数
另外,由于 inline 的作用其实就是将 inline 函数当中的代码拷贝到调用处,所以只有 public 的方法才可以使用 inline。
Public-API inline function cannot access non-public API xxx
2016-06-01
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/5549261.html

浙公网安备 33010602011771号