Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between crossinline and noinline in Kotlin?

Tags:

kotlin

  1. This code compiles with a warning (insignificant performance impact):

    inline fun test(noinline f: () -> Unit) {     thread(block = f) } 
  2. This code does not compile (illegal usage of inline-parameter):

    inline fun test(crossinline f: () -> Unit) {     thread(block = f) } 
  3. This code compiles with a warning (insignificant performance impact):

    inline fun test(noinline f: () -> Unit) {     thread { f() } } 
  4. This code compiles with no warning or error:

    inline fun test(crossinline f: () -> Unit) {     thread { f() } } 

Here are my questions:

  • How come (2) does not compile but (4) does?
  • What exactly is the difference between noinline and crossinline?
  • If (3) does not generates a no performance improvements, why would (4) do?
like image 707
Salomon BRYS Avatar asked Aug 08 '16 10:08

Salomon BRYS


People also ask

What is Noinline in Kotlin?

The noinline Effect By default, the inline keyword will instruct the compiler to inline the method call and all passed lambda functions on the call site: inline fun executeAll(action1: () -> Unit, action2: () -> Unit) { // omitted }

What is Crossline in Kotlin?

The crossinline marker is used to mark lambdas that mustn't allow non-local returns, especially when such lambda is passed to another execution context such as a higher order function that is not inlined, a local object or a nested function. In other words, you won't be able to do a return in such lambdas.

What is inline function in Kotlin Mindorks?

Inline function instruct compiler to insert complete body of the function wherever that function got used in the code. Advantages of inline function are as follows: Function call overhead doesn't occur. It also saves the overhead of push/pop variables on the stack when the function is called.

What is reified in Kotlin?

"reified" is a special type of keyword that helps Kotlin developers to access the information related to a class at runtime. "reified" can only be used with inline functions. When "reified" keyword is used, the compiler copies the function's bytecode to every section of the code where the function has been called.


2 Answers

From the inline functions reference:

Note that some inline functions may call the lambdas passed to them as parameters not directly from the function body, but from another execution context, such as a local object or a nested function. In such cases, non-local control flow is also not allowed in the lambdas. To indicate that, the lambda parameter needs to be marked with the crossinline modifier

Hence, example 2. doesn't compile, since crossinline enforces only local control flow, and the expression block = f violates that. Example 1 compiles, since noinline doesn't require such behavior (obviously, since it's an ordinary function parameter).

Examples 1 and 3 do not generate any performance improvements, since the only lambda parameter is marked noinline, rendering the inline modifier of the function useless and redundant - the compiler would like to inline something, but everything that could be has been marked not to be inlined.

Consider two functions, A and B

A

inline fun test(noinline f: () -> Unit) {     thread { f() } } 

B

fun test(f: () -> Unit) {     thread { f() } } 

Function A behaves like function B in the sense that the parameter f will not be inlined (the B function doesn't inline the body of test whereas in the A function, the body: thread { f() } still gets inlined).

Now, this is not true in the example 4, since the crossinline f: () -> Unit parameter can be inlined, it just cannot violate the aforementioned non-local control flow rule (like assigning new value to a global variable). And if it can be inlined, the compiler assumes performance improvements and does not warn like in the example 3.

like image 52
maciekjanusz Avatar answered Sep 24 '22 01:09

maciekjanusz


Let me try to explain this by example: I'll go through each of your examples and describe what it orders the compiler to do. First, here's some code that uses your function:

fun main(args: Array<String>) {     test {          println("start")         println("stop")     } } 

Now let's go through your variants. I'll call the functions from your examples test1..test4 and I'll show in pseudocode what the above main function would compile into.

1. noinline, block = f

inline fun test1(noinline f: () -> Unit) {     thread(block = f) }  fun compiledMain1() {     val myBlock = {         println("start")         println("stop")     }     thread(block = myBlock) } 

First, note there's no evidence of inline fun test1 even existing. Inline functions aren't really "called": it's as if the code of test1 was written inside main(). On the other hand, the noinline lambda parameter behaves same as without inlining: you create a lambda object and pass it to the thread function.

2. crossinline, block = f

inline fun test2(crossinline f: () -> Unit) {     thread(block = f) }  fun compiledMain2() {     thread(block =         println("start")         println("stop")     ) } 

I hope I managed to conjure what happens here: you requested to copy-paste the code of the block into a place that expects a value. It's just syntactic garbage. Reason: with or without crossinline you request that the block be copy-pasted into the place where it's used. This modifier just restricts what you can write inside the block (no returns etc.)

3. noinline, { f() }

inline fun test3(noinline f: () -> Unit) {     thread { f() } }  fun compiledMain3() {     val myBlock = {         println("start")         println("stop")     }     thread { myBlock() } } 

We're back to noinline here so things are straightforward again. You create a regular lambda object myBlock, then you create another regular lambda object that delegates to it: { myBlock() }, then you pass this to thread().

4. crossinline, { f() }

inline fun test4(crossinline f: () -> Unit) {     thread { f() } }  fun compiledMain4() {     thread {         println("start")         println("stop")     } } 

Finally this example demonstrates what crossinline is for. The code of test4 is inlined into main, the code of the block is inlined into the place where it's used. But, since it's used inside the definition of a regular lambda object, it can't contain non-local control flow.

About the Performance Impact

The Kotlin team wants you to use the inlining feature sensibly. With inlining the size of the compiled code can explode dramatically and even hit the JVM limits of up to 64K bytecode instructions per method. The main use case is higher-order functions that avoid the cost of creating an actual lambda object, only to discard it right after a single function call which happens right away.

Whenever you declare an inline fun without any inline lambdas, inlining itself has lost its purpose. The compiler warns you about it.

like image 43
Marko Topolnik Avatar answered Sep 22 '22 01:09

Marko Topolnik