Codementor Events

Effective Kotlin: Consider inline modifier for higher-order functions

Published Apr 23, 2018Last updated Oct 20, 2018
Effective Kotlin: Consider inline modifier for higher-order functions

Originally published on blog.kotlin-academy.com by Marcin Moskała

You might have noticed that all collection processing functions are inline. Have you ever asked yourself why are they defined in this way? Here is, for example, simplified filter function from Kotlin stdlib:

inline fun <T> Iterable<T>.filter(predicate: (T)->Boolean): List<T>{
    val destination = ArrayList<T>()
    for (element in this) 
        if (predicate(element))
            destination.add(element)
    return destination
}

How important is this inline modifier? Let’s say that we have 50k products and we need to sum price of the ones that were bought. We can do it simply by:

users.filter { it.bought }.sumByDouble { it.price }

In my machine, it takes 0.821s to calculate on average. How much would it be if this function were not inline? 0.940s on average on my machine. Check it out yourself. This doesn’t look like a lot, but you can notice this ~10% difference every time when you use methods for collection processing.

A much bigger difference can be observed when we modify local variables in lambda expression. Compare below functions:

inline fun repeat(times: Int, action: (Int) -> Unit) {
    for (index in 0 until times) {
        action(index)
    }
}

fun noinlineRepeat(times: Int, action: (Int) -> Unit) {
    for (index in 0 until times) {
        action(index)
    }
}

You might have noticed that except name, the only difference is that first does have inline modifier while second doesn’t. Usage is also the same:

var a = 0
repeat(100_000_000) { 
    a += 1
}

var b = 0
noinlineRepeat(100_000_000) { 
    b += 1
}

Although here big difference execution time. inlineRepeat have finished in 0.335 ns on average, while noinlineRepeat needed 156407036.930 ns on average. It is 466 thousand times more! Check out it yourself.

Why is this so important? Are there any costs of this performance improvement? When should we actually use inline modifier? These are very important questions and we will try to answer them. Although everything needs to start from the much more basic question: What does inline modifier do?

What does inline modifier do?

We know how functions are normally invoked. Execution jumps into function body, invokes all statements and then jumps back to the place where function was invoked.

Although when function is marked with inline modifier, compiler treats it differently. During code compilation, it replaces such function invocation with its body. print is inline function:

public inline fun print(message: Int) {
    System.out.print(message)
}

When we define following main:

fun main(args: Array<String>) {
    print(2)
    print(2)
}

After compilation it will look like this:

fun main(args: Array<String>) {
    System.out.print(2)
    System.out.print(2)
}

There is a slight difference that comes from the fact that we don’t need to jump another function and back. Although this effect is negligible. This is why you will have following warning in IDEA IntelliJ when defining such inline function yourself:

Why does IntelliJ suggest to use inline when we have lambda parameters? Because when we inline function body, we don’t need to create lambdas from arguments and instead we can inline them into calls. This invocation of above repeat function:

repeat(100) { println("A") }

Will look after compilation like this:

for (index in 0 until 1000) {
    println("A")
}

As you can see, body of lambda expression replaces calls in inline function. Let’s see another example. This filter function usage:

val users2 = users.filter { it.bought }

Will be replaced with:

val destination = ArrayList<T>()
for (element in this) 
    if (predicate(element))
        destination.add(element)
val users2 = destination

This is an important improvement. It is because JVM doesn’t support lambda expressions naively. It is pretty complex to explain how lambda expressions are compiled, but in general, there are two options:

  • Anonymous class
  • Separate class

Let’s see it in the example. We have following lambda expression:

val lambda: ()->Unit = {
    // body
}

It can turned out to be JVM anonymus class:

// Java
Function0 lambda = new Function0() { public Object invoke() {
      // code
   }
};

Or it can turn out to be normal class defined in a separate file:

// Java
// Additional class in separate file
public class TestInlineKt$lambda implements Function0 {
   public Object invoke() {
      // code
   }
}

// Usage
Function0 lambda = new TestInlineKt$lambda()

Second option is faster and it is used whenever possible. First option (anonymous class) is necessary when we need to use local variables.

This is an answer why we have such a difference between repeat and noinlineRepeat when we modify local variables. Lambda in non-inline function needs to be compiled into anonymous class. This is a huge cost because both their creation and usage is slower. When we use inline function then we don’t need to create any additional class at all. Check it yourself. Compile and decompile to Java this code:

fun main(args: Array<String>) {
    var a = 0
    repeat(100_000_000) { 
        a += 1
    } 
    var b = 0
    noinlineRepeat(100_000_000) { 
        b += 1
    }
}

You will find something similar to this:

// Java
public static final void main(@NotNull String[] args) {
   int a = 0;
   int times$iv = 100000000;
   int var3 = 0;

   for(int var4 = times$iv; var3 < var4; ++var3) {
      ++a;
   }

   final IntRef b = new IntRef();
   b.element = 0;
   noinlineRepeat(100000000, (Function1)(new Function1() { 
       public Object invoke(Object var1) {
           ++b.element;
           return Unit.INSTANCE;
      }
   }));
}

In filter example, improvement is not so vivid because lambda expression in non-inline version is compiled to normal class. It’s creation and usage is fast, but there is still a cost so we have this ~10% difference.

Cost of inline modifier

Inline should not be used too often because it also has its cost. Let’s say that I really like printing 2. I first defined the following function:

inline fun twoPrintTwo() {
    print(2)
    print(2)
}

It wasn’t enough for me, so I added this function:

inline fun twoTwoPrintTwo() {
    twoPrintTwo()
    twoPrintTwo()
}

Still not satisfied. I’ve defined following functions:

inline fun twoTwoTwoPrintTwo() {
    twoTwoPrintTwo()
    twoTwoPrintTwo()
}

fun twoTwoTwoTwoPrintTwo() {
    twoTwoTwoPrintTwo()
    twoTwoTwoPrintTwo()
}

Then I’ve decided to check what do I have in compiled code, so I compiled it to JVM bytecode and decompiled it to Java. twoTwoPrintTwo was already quite big:

public static final void twoTwoPrintTwo() {
   byte var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
}

But twoTwoTwoTwoPrintTwo was really scary:

public static final void twoTwoTwoTwoPrintTwo() {
   byte var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
   var1 = 2;
   System.out.print(var1);
}

This shows main problem with inline functions: code grows really quickly when we overuse them. This is actually a reason why IntelliJ gives a warning when we use them and improvement is unlikely.

Different aspects of inline modifier usage

Inline modifier, because of its character, changes much more then what we’ve seen in this article. It allows non-local return and reified generic type. It also has some limitations. Although this is not connected to Effective Kotlin series and this is a material for a separate article. If you want me to write it, express it on Twitter or in the comment.

When, in general, should we use inline modifier?

The most important case when we use inline modifier is when we define util-like functions with parameter functions. Collection or string processing (like filter, map or joinToString) or just standalone functions (like repeat) are perfect example.

This is why inline modifier is mostly an important optimization for library developers. They should know how does it work and what are its improvements and costs. We will use inline modifier in our projects when we define our own util functions with function type parameters.

When we don’t have function type parameter, reified type parameter, and we don’t need non-local return, then we most likely shouldn’t use inline modifier. This is why we will have a warning on Android Studio or IDEA IntelliJ.

Other cases are more complicated and we need to base on intuition. Later in this series, we will see that sometimes it is not clear which optimization is better. In such cases, we need to base on measurements or someone’s expertise.

Effective Kotlin

This is the first article about Effective Kotlin. When we see interest, we will publish next parts. In Kotlin Academy we also work on the book about this subject.

It will cover a much wider range of topics and go much deeper into every single one of them. It will include also best practices published by Kotlin and Google team, experiences of members of Kotlin team we cooperate with, and subjects touched in “Effective Java in Kotlin” series. To support it and make us publish it faster, use link and subscribe.


If you need a help with Kotlin, remember that I give consultations.

To be up-to-date with great news on Kotlin Academy, subscribe to the newsletter and observe Twitter.

To reference me on Twitter, use @MarcinMoskala. Use below link to subscribe to newsletter:

I would like to thank Ilya Ryzhenkov for corrections and important suggestions.

Discover and read more posts from Kotlin Academy
get started
post commentsBe the first to share your opinion
Show more replies