Exploring Kotlin’s hidden costs — Part 1

Lambda expressions and companion objects

In 2016, Jake Wharton gave a series of interesting talks about Java’s hidden costs. Around the same period he also started advocating the use of the Kotlin language for Android development but barely mentioned the hidden costs of that language, aside from recommending the use of inline functions. Now that Kotlin is officially supported by Google in Android Studio 3, I thought it would be a good idea to write about that aspect of the language by studying the bytecode it produces.

Kotlin is a modern programming language with a lot more syntactic sugar compared to Java, and as such there is equally more “black magic” going on behind the scenes, some of it having non-negligible costs, especially for Android development targeting older and lower-end devices.

This is not a case against Kotlin: I like the language a lot and it increases productivity, but I also believe a good developer needs to know how language features work internally in order to use them more wisely. Kotlin is powerful and some famous quote says:

“With great power comes great responsibility.”

These articles will focus solely on the JVM/Android implementation of Kotlin 1.1, not the Javascript implementation.

Kotlin Bytecode inspector

This is the tool of choice to figure out how Kotlin code gets translated to bytecode. With the Kotlin plugin installed in Android Studio, select the “Show Kotlin Bytecode” action to open a panel showing the bytecode of the current class. You can then press the “Decompile” button in order to read the equivalent Java code.

In particular, I’ll mention each time a Kotlin feature involves:

Boxing primitive types, which allocates short-lived objects

Instantiating extra objects not directly visible in code

Generating extra methods. As you may know, in Android applications the number of allowed methods inside a single dex file is limited, and going above it requires configuring multidex which comes with limitations and performance penalties, particularly on Android versions before Lollipop.

A note about benchmarks

I deliberately chose not to publish any microbenchmark, because most of them are meaningless, flawed, or both and cannot apply to all code variations and runtime environments. Negative performance impact will usually be amplified when the concerned code is used in loops or nested loops.

Furthermore, execution time is not the only thing to measure: increased memory usage has to be taken into consideration as well, because all allocated memory has to be reclaimed eventually and the cost of garbage collection depends on many factors like the available memory and the GC algorithm being used on the platform.

In a nutshell: if you want to know if a Kotlin construct has some noticeable speed or memory impact, measure your own code on your own target platform.