Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will immutable objects with const parameters be optimized to be instantiated only once by the Kotlin compiler

Tags:

There are many immutable classes in Java like String and primitive wrapper classes, and Kotlin introduced many others like Range subclasses and immutable Collection subclasses.

For iterating Ranges, from Control Flow: if, when, for, while - Kotlin Programming Language we already know:

A for loop over a range or an array is compiled to an index-based loop that does not create an iterator object.

However in other scenarios when dealing with Ranges this optimization is not possible.

When creating such immutable classes with const parameters, or more generally, recursively with const parameters, instantiating the class only once will bring performance gains. (In other words, if we call this a const immutable instantiation, an instantiation is a const immutable instantiation if and only if all its parameters are either constants or const immutable instantiations.) Since the Java compiler doesn't have a mechanism to know whether a class is immutable, does the Kotlin compiler optimize such classes to be instantiated only once, based on its knowledge of its known immutable classes?

For a more specific example of application, consider the following code:

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

Would the second one bring any performance improvements?

like image 225
Shreck Ye Avatar asked Nov 29 '19 10:11

Shreck Ye


People also ask

How do you make an object immutable in Kotlin?

The const modifier in Kotlin is used for compile-time constants. Immutability is done with a val keyword. You don't have to hide or delete somehow any setters of val properties as such properties don't have setters.

What does immutable mean in Kotlin?

What does Immutable mean? By definition, immutable means that, once created, an object/variable can't be changed. So, instead of changing a property of an object, you have to make a copy (or clone) of the entire object and in the process, change the property in question.

Why is Kotlin immutable?

An important invariant that Kotlin/Native runtime maintains is that the object is either owned by a single thread/worker, or it is immutable (shared XOR mutable). This ensures that the same data has a single mutator, and so there is no need for locking to exist.


1 Answers

I think the best thing you can do is to check what instructions the compiler generates.

Let's take the following source code:

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

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

For insideRepeat it will generate something like (I added a few comments):

    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

While for the outsideRepeat it will generate:

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

So it seems like the second version brings performance improvements indeed (also considering the GC will need to deallocate less objects)

like image 152
user2340612 Avatar answered Nov 15 '22 04:11

user2340612