【问题标题】:Will immutable objects with const parameters be optimized to be instantiated only once by the Kotlin compiler具有 const 参数的不可变对象是否会被优化为仅由 Kotlin 编译器实例化一次
【发布时间】:2019-11-29 10:58:05
【问题描述】:

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

对于Ranges,我们已经知道从Control Flow: if, when, for, while - Kotlin Programming Language迭代:

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

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

当使用 const 参数创建此类不可变类时,或者更一般地,使用 const 参数递归创建类时,只实例化一次类将带来性能提升。 (换句话说,如果我们称其为 const 不可变实例化,则当且仅当它的所有参数都是常量或 const 不可变实例化时,实例化才是 const 不可变实例化。)因为 Java 编译器没有一种机制来知道是否一个类是不可变的,Kotlin 编译器是否会根据其已知的不可变类的知识将此类类优化为只实例化一次?

对于更具体的应用示例,请考虑以下代码:

repeat(1024) {
    doSomething(('a'..'z').random())
}
val LOWERCASE_ALPHABETS = 'a'..'z'
repeat(1024) {
    doSomething(LOWERCASE_ALPHABETS.random())
}

第二个会带来性能提升吗?

【问题讨论】:

  • Kotlin 会将许多范围优化为正常的 for 循环,避免范围对象的实例化。我不知道在这些情况下是否会发生这种情况,但我认为它会发生。

标签: java kotlin constants immutability kotlinc


【解决方案1】:

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

我们来看下面的源码:

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

fun outsideRepeat() {
    val range = 'a'..'z'
    repeat(1024) {
        doSomething(range.random())
    }
}

对于insideRepeat,它会生成类似的东西(我添加了一些 cmets):

    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

而对于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

看来第二个版本确实带来了性能提升(同时考虑到 GC 将需要释放更少的对象)

【讨论】:

  • 这完全有可能在运行时得到优化以获得完全相同的性能。
  • 确实如此,但我想这也取决于您使用的特定 JVM 实现(或者更确切地说是 JIT 编译器),不是吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-26
  • 1970-01-01
相关资源
最近更新 更多