marko devcic

Software Engineer
  • github:
    deva666
  • email:
    madevcic {at} gmail.com
Kotlin overhead

Kotlin overhead

Posted on 01/17/2018

Kotlin has a lot of language features and library functions that make your life as a developer easier. And in this post, I want to see what does the Kotlin compiler do with these features that we use daily.
Before we start, I’m not saying that Kotlin is slow or these function calls will slow down your app. This is just a look at the Java bytecode that Kotlin compiler produces. Most of the examples here show how Kotlin allocates additional objects on the Heap. And this shouldn't be a problem, allocating objects on the heap on managed runtimes is really fast.
What could be the problem is when the time comes to collect those objects, Garbage collector will have to kick in and halt all the threads in your app (there are various GC implementations, also different runtimes, but all try to minimize the duration of the pause when the GC is reclaiming unreachable objects).
Of course, if you think you have performance issues, you should first measure and then identify where these problems come from. One more thing worth mentioning is that this is looking at the Java bytecode when you are targeting Java 6 (what you would currently get using Kotlin on Android) with Kotlin version 1.2.


Companion objects

These are singleton objects nested inside a class. Most people use them for declaring constants for the outer containing class. When you declare a companion object inside another class, like this example

class Outer {
    companion object {
	private const val CONSTANT = “kotlin”
    }
}

Kotlin compiler will produce bytecode that would look like this if it was reversed back to Java

public final class Outer {
    private static final String CONSTANT = “kotlin”;
    public static final Outer.Companion Companion = new Outer.Companion();
	
    public static final class Companion {
        private Companion() {
	}
    }
}

The costs of companion object are One additional object created (no matter how many instances of outer class you have because it is static). And if you are on Android you might worry about method count. This adds additional 2 methods (empty constructor and inside a constructor, call to base Object class constructor). Interesting is how the compiler pulls the constant outside the companion Object and puts it inside the Outer, containing class.
If you have a lot of companion objects, are worried about this extra object and you use companion objects for declaring constants, you can also declare them on a File level, but outside of the class. Kotlin allows this and code would look like this.

private const val CONSTANT = “kotlin”
class Outer {
   private val str: String = CONSTANT // private fields can be accessed if in same file
}

And the byte code would look like this in Java.

public final class Outer {
   private final String str = "";
}
public final class CompanionObjectKt {
   private static final String CONSTANT = "";
}

Lambdas

Java also has lambdas since Java 8, but Kotlin can compile to Java 6 compatible byte code. So how come then Kotlin has lambdas if they didn’t exist with Java 6?
A compiler trick. Whenever compiler sees a lambda, it will transform it, into an anonymous inner class.
So, whenever you declare a lambda, Kotlin will create one extra object on the heap. But these objects are singletons, so on the first use of lambda, an object representing the inner class gets created and then all subsequent invocations of this lambda will use the same object.
Now if you are Android, you would have to use Anonymous classes either way so there is no actual difference. Kotlin also has an inline keyword, which if applied to a function which accepts a function type as a parameter, will inline the function body passed as an argument directly into a call site.


lazy function

Kotlin has a cool feature, delegated properties. Really useful implementation of delegate property is the lazy function. Which initializes the property on first access instead of when the containing class is initialized.

 val lazyProperty by lazy { "this will get called when you access it for the first time" }
It can be really handy because you don’t have to use lateinit and mutable var property. The cost is that instead of 1 object (or none if your property is a primitive type) you’ll end up with 2 or 3 objects for each lazy (depending if your property is primitive or not). It needs one extra object for the lambda that initializes the property (function type is not inlined) and one extra for the Delegated property.


mutableListOf or listOf function

Kotlin standard librady has handy functions for createing List and MutableList objects with listOf and mutableListOf functions which accept a variable number of arguments.
You can initialize a list interface and populuate it like this:
val list = listOf(1,2,3)
You'll have two objects allocated, one for the list itself and one for an Array that holds variable arguments of the function.


mapOf function

Similar to the listOf and mutableListOf functions, you get one extra object for the variable arguments list. Plus one extra object for each key value pair you supply in the arguments list.

 val map = mapOf(1 to "one", 2 to "two")
The reason is Kotlin uses Pair object initialize a key value pair.


Coroutines

Kotlin coroutines are similar to C# async/await, basically, they are suspendable functions. Functions that can be suspended midway through execution and later resumed at the suspension point. Kotlin’s implementation borrows ideas from C#. Whenever you declare a coroutine the compiler will generate a state machine for keeping track of the locals and resumption points, so that’s at least one extra object per coroutine.


String Interpolation

Given this Kotlin function:

fun interpolation(foo: Int) {
   print("$foo")
}

IntelliJ Kotlin decompiler tool showed this Bytecode

 public final static interpolation(I)V
   L0
    LINENUMBER 2 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder. ()V
    LDC ""
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ILOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKESTATIC kotlin/io/ConsoleKt.print (Ljava/lang/Object;)V
   L1
    LINENUMBER 3 L1
    RETURN
   L2
    LOCALVARIABLE foo I L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1 

Which used a StringBuilder eventhough there was only one argument in the String literal.
Compiling the same file with a command line tool and then decompiling it shows that compiler does optimize and didn't use a StringBuilder when one argument is inside a String literal.

public static final void interpolation(int foo) { kotlin.io.ConsoleKt.print(String.valueOf(foo)); }

So, depending on the number of arguments inside a String literal, Kotlin may decide to build your String with a StringBuilder.

Happy coding!