What is the difference between crossinline and noinline in Kotlin?

后端 未结 4 1695
死守一世寂寞
死守一世寂寞 2021-01-30 15:42
  1. This code compiles with a warning (insignificant performance impact):

    inline fun test(noinline f: () -> Unit) {
        thread(         
    
    
            
4条回答
  •  情歌与酒
    2021-01-30 16:26

    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) {
        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.

提交回复
热议问题