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