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, a 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 5 000 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 38 ms to calculate on average. How much would it be if this function were not inline ? 42 ms 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

}

Here we have a big difference in execution time. inlineRepeat have finished in 0.335 ns on average, while noinlineRepeat needed 153 980 484.884 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 a function body, invokes all statements and then jumps back to the place where a function was invoked.

Although when a 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.

Collection stream processing vs classic way

Inline modifier is a key element that makes collection processing in a stream way as effective as classic processing that base on loops. It was tested again and again and always classic processing is a huge cost in terms of code readability and small or no improvement in terms of performance. For instance, below code:

return data.filter { filterLoad(it) }.map { mapLoad(it) }

Works the same and have the same execution time as this one:

val list = ArrayList<String>()

for (it in data) {

if (filterLoad(it)) {

val value = mapLoad(it)

list.add(value)

}

}

return list

Concrete results from benchmark measurements (code is here):

Benchmark (size) Mode Cnt Score Error Units

filterAndMap 10 avgt 200 561.249 ± 1 ns/op

filterAndMap 1000 avgt 200 29803.183 ± 127 ns/op

filterAndMap 100000 avgt 200 3859008.234 ± 50022 ns/op



filterAndMapManual 10 avgt 200 526.825 ± 1 ns/op

filterAndMapManual 1000 avgt 200 28420.161 ± 94 ns/op

filterAndMapManual 100000 avgt 200 3831213.798 ± 34858 ns/op

From program point of view, this two functions are nearly equal. Although from readability point of view first option is much better. This is why we should always prefer to use smart collection processing functions instead of implementing whole processing ourselves. Also if we need some other collection processing function that is not is stdlib, do not hesitate to write your own function. This is, for instance, function I’ve added on my last project when I needed to transpose list of lists:

fun <E> List<List<E>>.transpose(): List<List<E>> {

if (isEmpty()) return this



val width = first().size

if (any { it.size != width }) {

throw IllegalArgumentException("All nested lists must have the same size, but sizes were ${map { it.size }}")

}



return (0 until width).map { col ->

(0 until size).map { row -> this[row][col] }

}

}

Just remember to write some unit tests:

class TransposeTest {



private val list = listOf(listOf(1, 2, 3), listOf(4, 5, 6))



@Test

fun `Transposition of transposition is identity`() {

Assert.assertEquals(list, list.transpose().transpose())

}



@Test

fun `Simple transposition test`() {

val transposed = listOf(listOf(1, 4), listOf(2, 5), listOf(3, 6))

assertEquals(transposed, list.transpose())

}

}

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();

}

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 Kot. 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 this link and subscribe.