The Economics of Kotlin Bytecode

Table of contents

Among other things, at the core of software development is optimization. The phrase "economics of bytecode" essentially captures the variance between manually written code and generated one, whereby even though the former is written with utmost precision to ensure it is 100% optimized, the latter does not always reflect the optimization.
Optimization of generated code is equally as important as optimization of manually written code, for the less code generated, the fewer object allocations done by the JVM, the more optimized your code will be. Expanding on this idea , I'll delve into a real-life example I have encountered.
Kotlin Standard Library Function:mapNotNullTo(destination:C):R
The Kotlin library provides an array of useful functions that ensure developers and coders alike get the job done with minimum effort as possible.
One of these functions is mapNotNullTo(destination: C, transform: (T) -> R?): C
which allows us to apply a given transformation function to elements in the original collection and pass the non-null results to another mutable collection which is the destination.
An example of the function usage is as follows.
Compilation of the example above gives us the following bytecode.
Since it is not easy to discern the bytecode we will decompile it using Intellij decompile tool, work with the decompiled version of the bytecode which is as follows.
There is a lot going on here so let's break it down line by line.
Line 3-4 initializes the lists we have just created.
Line 5 creates an iterable of the list holding our data. An iterable is what makes our list capable of being iterated.
Line 6 -7 initializes the $it
context object used within the transformation function of mapNotNullTo(destination: C, transform: (T) -> R?): C
(remember that scope functions such as lambda expressions must have context objects).
Line 8 initializes an iterator, from the iterable declared in line 5 , that is going to be used by the compiler to iterate the list from the beginning to the end.
Lin 10 declares a while loop and line 11 uses the next()
method provided by iterators to get the next element in the collection.
Line 12 and 14 represents the elements in our list that are null
.
Line 13 creates an integer from the elements gotten from next()
method, and if this integer is not null
it gets added into our destination container.
What we want to do is to de-duplicate lines 8, 5, 12, 14, 6 and 7. For lines 5 and 8 I will merge them into one variable rather than having the Iterable and Iterator being assigned to different variables in different lines. Whilst for lines 12, 14, 6 and 7 I will remove them completely by tweaking the code.
Of course one would argue that the de-duplication is insignificant but consider a scenario where you are using mapNotNullTo(destination: C, transform: (T) -> R?): C
function thrice in your code, this means you'll have at the very least 6 extra variables generated. Hence ending up creating a lot of bytecode bloat !
The sweet part of optimizing your bytecode is that you can actually infer from the bytecode to know where to tweak. For instance, one way of reducing the bytecode bloat is to use an iterator just as in the bytecode. We can define our own iterator in our manual code rather than letting the Kotlin compiler do that for us.
The changes to our original code would be as follows
And after compiling this code, our decompiled version would be as follows
And viola! From 21 lines of code to 13 lines that is almost half of the generated code! Amazing isn't it?
By refraining from using the mapNotNullTo(destination: C, transform: (T) -> R?): C
function and replacing it with an Iterator to iterate our list and a while loop, the compiler does not create the extra variables needed earlier.
It is noteworthy that the flexibility provided by kotlin standard library functions requires a little more code during compilation to make it JVM compatible hence the extra variables. Although, the kotlin compiler does optimize your code, the extra variables created may impact performance of your code in the long run as mentioned by Dmitry Jemerov in his presentation.
Nonetheless, that does not mean you refrain from using the Kotlin standard library functions.
You don't really need to dig this deep unless you are building a resource constrained application. I do these optimizations once in a while because they are fun :XD and you get to learn a lot about the mechanics of code compilation, but also because in the end it does pay off performance-wise!
Until next time. Happy coding ๐!
Subscribe to my newsletter
Read articles from Gibson Ruitiari directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Gibson Ruitiari
Gibson Ruitiari
I am kotlin enthusiast with an eye for android development and automation with the language. I am also conversant with python and Java but my tool of trade as of now is Kotlin. Currently, I am in school pursuing law but I am open to opportunities :-). I am passionate about kotlin and I am all about exploring the different ways the language can be applied other than android field.