具有const参数的不可变对象将被优化为仅由Kotlin编译器实例化一次

Shr*_* Ye 5 java constants immutability kotlin kotlinc

Java中有许多不可变的类,例如String原始包装器类,而Kotlin引入了许多其他Range不可变的Collection类,例如子类和不可变子类。

对于Ranges,从Control Flow:是否,何时,何时,何时-Kotlin编程语言,我们已经知道:

for在一定范围或数组环被编译为一个基于索引的循环,不创建一个迭代器对象。

但是,在处理Ranges的其他情况下,这种优化是不可能的。

当使用const参数或更一般地使用const参数递归创建此类不可变类时,仅实例化一次该类将带来性能提升。(换句话说,如果我们将其称为const不可变实例化,则且仅当其所有参数均为常量或const不变实例化时,实例化才是const不可变实例化。)由于Java编译器没有一种机制来知道是否一个类是不可变的,Kotlin编译器是否基于对已知不可变类的了解而将此类仅进行一次实例化?

有关应用程序的更具体示例,请考虑以下代码:

repeat(1024) {
    doSomething(('a'..'z').random())
}
Run Code Online (Sandbox Code Playgroud)
val LOWERCASE_ALPHABETS = 'a'..'z'
repeat(1024) {
    doSomething(LOWERCASE_ALPHABETS.random())
}
Run Code Online (Sandbox Code Playgroud)

第二个会带来任何性能改进吗?

use*_*612 4

我认为您能做的最好的事情就是检查编译器生成的指令。

我们来看下面的源代码:

fun insideRepeat() {
    repeat(1024) {
        doSomething(('a'..'z').random())
    }
}

fun outsideRepeat() {
    val range = 'a'..'z'
    repeat(1024) {
        doSomething(range.random())
    }
}
Run Code Online (Sandbox Code Playgroud)

因为insideRepeat它会生成类似的东西(我添加了一些评论):

    public final static insideRepeat()V
    L0
    LINENUMBER 2 L0
    SIPUSH 1024
    ISTORE 0
    L1
    L2
    ICONST_0
    ISTORE 1
    ILOAD 0
    ISTORE 2
    L3
    ILOAD 1
    ILOAD 2
    IF_ICMPGE L4 // loop termination condition
    L5
    ILOAD 1
    ISTORE 3
    L6
    ICONST_0
    ISTORE 4
    L7 // loop body
    LINENUMBER 3 L7
    BIPUSH 97
    ISTORE 5
    NEW kotlin/ranges/CharRange
    DUP
    ILOAD 5
    BIPUSH 122
    INVOKESPECIAL kotlin/ranges/CharRange.<init> (CC)V // new instance created inside the loop
    INVOKESTATIC FooKt.random (Lkotlin/ranges/CharRange;)Ljava/lang/Object;
    INVOKESTATIC FooKt.doSomething (Ljava/lang/Object;)Ljava/lang/Object;
    POP
Run Code Online (Sandbox Code Playgroud)

而对于它outsideRepeat会生成:

public final static outsideRepeat()V
L0
LINENUMBER 8 L0
BIPUSH 97
ISTORE 1
NEW kotlin/ranges/CharRange
DUP
ILOAD 1
BIPUSH 122
INVOKESPECIAL kotlin/ranges/CharRange.<init> (CC)V // range created outside loop
ASTORE 0
L1
LINENUMBER 9 L1
SIPUSH 1024
ISTORE 1
L2
L3
ICONST_0
ISTORE 2
ILOAD 1
ISTORE 3
L4
ILOAD 2
ILOAD 3
IF_ICMPGE L5 // termination condition
L6
ILOAD 2
ISTORE 4
L7
ICONST_0
ISTORE 5
L8
LINENUMBER 10 L8
ALOAD 0
INVOKESTATIC FooKt.random (Lkotlin/ranges/CharRange;)Ljava/lang/Object;
INVOKESTATIC FooKt.doSomething (Ljava/lang/Object;)Ljava/lang/Object;
POP
Run Code Online (Sandbox Code Playgroud)

所以看来第二个版本确实带来了性能改进(还考虑到 GC 需要释放更少的对象)