BradCypert.com
Lambda-Map Performance in Kotlin
November 19, 2018

Lambda-Map Performance in Kotlin

Posted on November 19, 2018  (Last modified on December 27, 2022 )
3 minutes  • 474 words
This project uses these versions of languages, frameworks, and libraries.
  • kotlin kotlin : 1.2.60
This tutorial may work with newer versions and possibly older versions, but has only been tested on the versions mentioned above.

Working on an enterprise application written in Kotlin, I’ve recently noticed something that felt “off” to me. Perhaps you’ve seen it, perhaps you’ve written it — maybe even recently! It looks something like this (I call this a Lambda-Map):

object Demo {
  fun main() {
       listOf(1,2,3,4).map {
            plusOne(it)
        }
    }

    private fun plusOne(i: Int): Int = i + 1
}

Sure, this plusOne function isn’t really necessary. We can just inline it in the Lambda, but imagine for just a moment that this function does much, much more than just add one to a number. It then feels just to have that code pulled out into a private function — but that’s not the offender here.

The offender here is the unnecessary lambda being used to call a private function in a map.

I feel like this happens due to the fact that Intellij’s default auto-complete for .map is a new lambda. Rightfully so, it looks nice, shows off how great the language is, and likely is used the most often. But in the case of the private function that does all of the work, It’s not needed.

object Demo {
  fun main() {
    listOf(1,2,3,4).map(::plusOne)
  }

  private fun plusOne(i: Int): Int = i + 1
}

This is similar, but should avoid a few unnecessary calls. If you’re unfamiliar with the :: syntax, fret not. These are called callableReference literals and can be used when you’d like to pass a named function as an argument. In this case, it negates the lambda passed into map completely, simply by directly calling the private function plusOne. We can even break this simple example down and see small improvements in the bytecode generated from each of these.

Bytecode Break

Here is the generated bytecode for the Lambda example. This is only the bytecode for the map call, specifically, and not the entire object/file.

LINENUMBER 4 L6
    GETSTATIC Demo.INSTANCE : LDemo;
    ILOAD 6
    INVOKESPECIAL Demo.plusOne (I)I
   L7
   L8
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    ASTORE 11
    ALOAD 10
    ALOAD 11
    INVOKEINTERFACE java/util/Collection.add (Ljava/lang/Object;)Z
    POP

And here’s the compiled bytecode for the callableReference literal.

LINENUMBER 4 L7
INVOKESPECIAL Demo.plusOne (I)I
L8
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 12
ALOAD 11
ALOAD 12
INVOKEINTERFACE java/util/Collection.add (Ljava/lang/Object;)Z
POP

Not only is it avoiding the GETSTATIC instruction, but it also removes an unnecessary integer load (ILOAD) into memory.

It’s certainly a micro-optimization on the performance scale, but:

  1. It could add up, depending on how many of .maps you use.
  2. Personally, I think it makes the code cleaner and easier to read.

Anyways, that’s an interesting tidbit that I feel Intellij doesn’t help you with very much (It can’t do everything for you). Hopefully this was helpful!

If you’d like to learn more about Kotlin, you can check out my other Kotlin posts here !

“Bytecode Break” was borrowed from this fantastic talk by Huyen Tue Dao .

Cartoon headshot of Brad Cypert
Follow me

Connect with me to follow along on my journey in my career, open source, and mentorship. Occasionally, I'll share good advice and content (quality not guaranteed).