Kotlin运行时性能

Kotlin整体的性能相对于Java而言毫不逊色,甚至在一些方面优于Java,本文参考这篇benchmark文章进行Kotlin性能相关总结,关于Kotlin对包大小影响、使用、选择原因等请参考之前的一篇Kotlin的文章,如果对于Java运行时性能感兴趣可以参考这篇文章

前言

根据benchmark文章所有的所有测试均采样200次,使用单位ops/ms(执行次数/毫秒)(因此数值是越大越好)并且均在以下环境:

  • Macbook Pro (2,5 GHz Intel Core i7, 16GB of RAM)
  • Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
  • Kotlin version (1.1.3)
  • JMH (0.5.6)

性能测试结果

1. 性能相比Java更差相关

  • varargs参数展开,Kotlin比Java慢1倍,主要原因是在Kotlin在展开varargs前需要全量拷贝整个数组,这个是非常高的性能开销。
  • Delegated Properties的应用,Kotlin相比Java慢10%

2. 性能相比Java更优相关

  • Lambda的使用,Kotlin相比Java快30%,而对用例中的transaction添加inline关键字配置内联后,发现其反而慢了一点点(约1.14%)。
  • Kotlin对companion object的访问相比Java中的静态变量的访问,Kotlin与Java差不多快或更快一点
  • Kotlin对局部函数(Local Functions)的访问相比Java中的局部函数的访问,Kotlin与Java差不多快或更快一点
  • Kotlin的非空参数的使用相比没有使用空检查的Java,Kotlin与Java差不多快或更快一点

3. Kotlin自身比较

  • 对于基本类型范围的使用,无论是否使用常量引用还是直接的范围速度都差不多
  • 对于非基本类型范围的使用,常量引用相比直接的范围会快3%左右
  • 对于范围遍历方式中,for循环方式无论有没有使用step速度都差不多,但是如果对范围直接进行.foreach速度会比它们慢3倍,因此避免对范围直接使用.foreach
  • 在遍历中使用lastIndex会比使用indices快2%左右

实验过程

I. 性能相比Java更差相关

1. varargs参数

测试发现: 对varargs参数展开,Kotlin比Java慢1倍,主要原因是在Kotlin在展开varargs前需要全量拷贝整个数组,这个是非常高的性能开销。

测试用例Kotlin代码:

1
2
3
4
5
6
7
8
9
fun runPrintDouble(blackHole: BlackHole, values: IntArray) {
printDouble(blackHole, *values)
}
fun printDouble(blackHole: BlackHole, vararg values: Int) {
for (value in values) {
blackHole.consume(value)
}
}

测试用例Java代码:

1
2
3
4
5
6
7
8
9
public static void runPrintDouble( BlackHole blackHole, int[] values ) {
printDouble( blackHole, values );
}
public static void printDouble( BlackHole blackHole, int... values ) {
for (int value : values) {
blackHole.consume( value );
}
}

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
javaIntVarargs173265.270260.837
kotlinIntVarargs83621.509990.854

2. Delegated Properties

测试发现:对Delegated Properties的应用,Kotlin相比Java慢10%

测试用例Kotlin代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class StringDelegate {
private var cache: String? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
var result = cache
if (result == null) {
result = someOperation()
cache = result
}
return result!!
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
cache = value
}
}
class Example {
var p: String by StringDelegate()
}
fun runStringDelegateExample(blackHole: BlackHole) {
val example = Example()
blackHole.consume(example.p)
blackHole.consume(example.p)
}

测试用例Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class DelegatePropertyTest {
public static String stringValue = "hello";
public static String someOperation() {
return stringValue;
}
}
class Example2 {
public String p;
public void initialize() {
p = DelegatePropertyTest.someOperation();
}
}
public static void runStringDelegateExample( BlackHole blackHole ) {
Example2 example2 = new Example2();
example2.initialize();
blackHole.consume( example2.p );
blackHole.consume( example2.p );
}

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
javaSimplyInitializedProperty274394.088554.171
kotlinDelegateProperty255899.824910.112

II. 性能相比Java更优相关

1. Lambda

由于Lambda是在Java8中引入,所以对比的是Java8与Kotlin1.1.3

测试发现:对Lambda的使用,Kotlin相比Java快30%,而对用例中的transaction添加inline关键字配置内联后,发现其反而慢了一点点(约1.14%)。

测试用例Kotlin代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun transaction(db: Database, body: (Database) -> Int): Int {
db.beginTransaction()
try {
val result = body(db)
db.setTransactionSuccessful()
return result
} finally {
db.endTransaction()
}
}
fun kotlinLambda() {
val deletedRows = transaction(db) {
it.delete("Customers", null, null)
}
}

测试用例Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static int transaction( Database db, ToIntFunction<Database> body ) {
db.beginTransaction();
try {
int result = body.applyAsInt( db );
db.setTransactionSuccessful();
return result;
} finally {
db.endTransaction();
}
}
void javaLambda() {
int deletedRows = transaction( db, ( database ) ->
database.delete( "Customer", null, null ) );
}

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
javaLambda1024302.4091851.789
kotlinInlinedFunction1344885.4452632.587
kotlinLambda1362991.1212824.862

2. 静态(Companion Objects)变量访问

测试发现:Kotlin对companion object的访问相比Java中的静态变量的访问,Kotlin与Java差不多快或更快一点

测试用例Kotlin代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass private constructor() {
companion object {
private val TAG = "TAG"
fun newInstance() = MyClass()
}
fun helloWorld() = TAG
}
fun runCompanionObjectCallToPrivateConstructor(): String {
val myClass = MyClass.newInstance()
return myClass.helloWorld()
}

测试用例Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyJavaClass {
private static final String TAG = "TAG";
private MyJavaClass() {
}
public static String helloWorld() {
return TAG;
}
public static MyJavaClass newInstance() {
return new MyJavaClass();
}
}
public static String runPrivateConstructorFromStaticMethod() {
MyJavaClass myJavaClass = newInstance();
return myJavaClass.helloWorld();
}

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
javaPrivateConstructorCallFromStaticMethod398709.154800.190
kotlinPrivateConstructorCallFromCompanionObject404746.375621.591

3. 局部函数(Local Functions)访问

测试发现:Kotlin对局部函数的访问相比Java中的局部函数的访问,Kotlin与Java差不多快或更快一点

测试用例Kotlin代码:

1
2
3
4
5
6
7
8
9
10
11
fun kotlinLocalFunctionCapturingLocalVariable(a: Int): Int {
fun sumSquare(b: Int) = (a + b) * (a + b)
return sumSquare(1) + sumSquare(2)
}
fun kotlinLocalFunctionWithoutCapturingLocalVariable(a: Int): Int {
fun sumSquare(a: Int, b: Int) = (a + b) * (a + b)
return sumSquare(a, 1) + sumSquare(a, 2)
}

测试用例Java代码:

1
2
3
4
5
public static int javaLocalFunction( int a ) {
IntUnaryOperator sumSquare = ( int b ) -> ( a + b ) * ( a + b );
return sumSquare.applyAsInt( 1 ) + sumSquare.applyAsInt( 2 );
}

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
javaLocalFunction897015.9561951.104
kotlinLocalFunctionCapturingLocalVariable909087.3561690.368
kotlinLocalFunctionWithoutCapturingLocalVariable908852.8701822.557

4. 空检查(Null safety)

测试发现:Kotlin的非空参数的使用相比没有使用空检查的Java,Kotlin与Java差不多快或更快一点

测试用例Kotlin代码:

1
fun sayHello(who: String, blackHole: BlackHole) = blackHole.consume("Hello $who")

测试用例Java代码:

1
2
3
public static void sayHello( String who, BlackHole blackHole ) {
blackHole.consume( "Hello " + who );
}

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
javaSayHello73353.725155.551
kotlinSayHello75637.556162.963

III. Kotlin自身比较

1. 基本类型范围

测试发现: 对于基本类型范围的使用,无论是否使用常量引用还是直接的范围速度都差不多

常量引用基本类型范围用例:

1
2
3
private val myRange get() = 1..10
fun isInOneToTenWithIndirectRange(i: Int) = i in myRange

直接引用基本类型范围的用例:

1
fun isInOneToTenWithLocalRange(i: Int) = i in 1..10

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
kotlinIndirectRange1214464.5622071.128
kotlinLocallyDeclaredRange1214883.4111797.921

2. 非基本类型范围

测试发现: 对于非基本类型范围的使用,常量引用相比直接的范围会快3%左右

常量引用非基本类型范围用例:

1
2
3
4
5
private val NAMES = "Alfred".."Alicia"
fun isBetweenNamesWithConstantRange(name: String): Boolean {
return name in NAMES
}

直接引用非基本类型范围的用例:

1
2
3
fun isBetweenNamesWithLocalRange(name: String): Boolean {
return name in "Alfred".."Alicia"
}

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
kotlinStringRangeInclusionWithLocalRange211468.439483.879
kotlinStringRangeInclusionWithConstantRange218073.886412.408

3. 范围遍历

测试发现: 对于范围遍历方式中,for循环方式无论有没有使用step速度都差不多,但是如果对范围直接进行.foreach速度会比它们慢3倍,因此避免对范围直接使用.foreach

for循环的用例:

1
2
3
4
5
fun rangeForEachLoop(blackHole: BlackHole) {
for (it in 1..10) {
blackHole.consume(it)
}
}

for循环并且加上step的用例:

1
2
3
4
5
fun rangeForEachLoopWithStep1(blackHole: BlackHole) {
for (it in 1..10 step 1) {
blackHole.consume(it)
}
}

对范围直接进行.foreach的用例:

1
2
3
4
5
fun rangeForEachMethod(blackHole: BlackHole) {
(1..10).forEach {
blackHole.consume(it)
}
}

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
kotlinRangeForEachFunction108382.188561.632
kotlinRangeForEachLoop331558.172494.281
kotlinRangeForEachLoopWithStep1331250.339545.200

4. 对于indices对比

测试发现: 使用lastIndex会比使用indices快2%左右

先创建一个SparseArray:

1
2
3
4
class SparseArray<out T>(val collection: List<T>) {
fun size() = collection.size
fun valueAt(index: Int) = collection[index]
}

使用indices的用例:

1
2
3
4
5
6
7
8
inline val SparseArray<*>.indices: IntRange
get() = 0..size() - 1
fun printValuesUsingIndices(map: SparseArray<String>, blackHole: BlackHole) {
for (i in map.indices) {
blackHole.consume(map.valueAt(i))
}
}

使用lastIndex的用例:

1
2
3
4
5
6
7
8
inline val SparseArray<*>.lastIndex: Int
get() = size() - 1
fun printValuesUsingLastIndexRange(map: SparseArray<String>, blackHole: BlackHole) {
for (i in 0..map.lastIndex) {
blackHole.consume(map.valueAt(i))
}
}

测试结果(每毫秒执行次数):

Benchmark平均值平均误差
kotlinCustomIndicesIteration79096.631134.813
kotlinIterationUsingLastIndexRange80811.554122.462


Jacksgong wechat
欢迎关注Jacks Blog公众号,第一时间接收原创技术沉淀干货。