本文最后更新于:2017年7月12日 凌晨
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) } }
KOTLIN
|
测试用例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 ); } }
JAVA
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
javaIntVarargs |
173265.270 |
260.837 |
kotlinIntVarargs |
83621.509 |
990.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) }
KOTLIN
|
测试用例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 ); }
JAVA
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
javaSimplyInitializedProperty |
274394.088 |
554.171 |
kotlinDelegateProperty |
255899.824 |
910.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) } }
KOTLIN
|
测试用例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 ) ); }
JAVA
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
javaLambda |
1024302.409 |
1851.789 |
kotlinInlinedFunction |
1344885.445 |
2632.587 |
kotlinLambda |
1362991.121 |
2824.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() }
KOTLIN
|
测试用例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(); }
JAVA
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
javaPrivateConstructorCallFromStaticMethod |
398709.154 |
800.190 |
kotlinPrivateConstructorCallFromCompanionObject |
404746.375 |
621.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) }
KOTLIN
|
测试用例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 ); }
JAVA
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
javaLocalFunction |
897015.956 |
1951.104 |
kotlinLocalFunctionCapturingLocalVariable |
909087.356 |
1690.368 |
kotlinLocalFunctionWithoutCapturingLocalVariable |
908852.870 |
1822.557 |
![]()
4. 空检查(Null safety)
测试发现:Kotlin的非空参数的使用相比没有使用空检查的Java,Kotlin与Java差不多快或更快一点。
测试用例Kotlin代码:
1
|
fun sayHello(who: String, blackHole: BlackHole) = blackHole.consume("Hello $who")
KOTLIN
|
测试用例Java代码:
1 2 3
|
public static void sayHello( String who, BlackHole blackHole ) { blackHole.consume( "Hello " + who ); }
JAVA
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
javaSayHello |
73353.725 |
155.551 |
kotlinSayHello |
75637.556 |
162.963 |
![]()
III. Kotlin自身比较
1. 基本类型范围
测试发现: 对于基本类型范围的使用,无论是否使用常量引用还是直接的范围速度都差不多。
常量引用基本类型范围用例:
1 2 3
|
private val myRange get() = 1..10
fun isInOneToTenWithIndirectRange(i: Int) = i in myRange
KOTLIN
|
直接引用基本类型范围的用例:
1
|
fun isInOneToTenWithLocalRange(i: Int) = i in 1..10
KOTLIN
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
kotlinIndirectRange |
1214464.562 |
2071.128 |
kotlinLocallyDeclaredRange |
1214883.411 |
1797.921 |
![]()
2. 非基本类型范围
测试发现: 对于非基本类型范围的使用,常量引用相比直接的范围会快3%左右。
常量引用非基本类型范围用例:
1 2 3 4 5
|
private val NAMES = "Alfred".."Alicia"
fun isBetweenNamesWithConstantRange(name: String): Boolean { return name in NAMES }
KOTLIN
|
直接引用非基本类型范围的用例:
1 2 3
|
fun isBetweenNamesWithLocalRange(name: String): Boolean { return name in "Alfred".."Alicia" }
KOTLIN
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
kotlinStringRangeInclusionWithLocalRange |
211468.439 |
483.879 |
kotlinStringRangeInclusionWithConstantRange |
218073.886 |
412.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) } }
KOTLIN
|
for循环并且加上step的用例:
1 2 3 4 5
|
fun rangeForEachLoopWithStep1(blackHole: BlackHole) { for (it in 1..10 step 1) { blackHole.consume(it) } }
KOTLIN
|
对范围直接进行.foreach的用例:
1 2 3 4 5
|
fun rangeForEachMethod(blackHole: BlackHole) { (1..10).forEach { blackHole.consume(it) } }
KOTLIN
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
kotlinRangeForEachFunction |
108382.188 |
561.632 |
kotlinRangeForEachLoop |
331558.172 |
494.281 |
kotlinRangeForEachLoopWithStep1 |
331250.339 |
545.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] }
KOTLIN
|
使用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)) } }
KOTLIN
|
使用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)) } }
KOTLIN
|
测试结果(每毫秒执行次数):
| Benchmark | 平均值 | 平均误差 |
kotlinCustomIndicesIteration |
79096.631 |
134.813 |
kotlinIterationUsingLastIndexRange |
80811.554 |
122.462 |
![]()
Kotlin运行时性能
https://blog.dreamtobe.cn/kotlin-performance/
https://blog.dreamtobe.cn/kotlin-performance/