when to use an inline function in Kotlin?

后端 未结 5 345
借酒劲吻你
借酒劲吻你 2020-12-04 05:50

I know that an inline function will maybe improve performance & cause the generated code to grow, but I\'m not sure when it is correct to use one.

lock(l         


        
相关标签:
5条回答
  • 2020-12-04 06:14

    Let's say you create a higher order function that takes a lambda of type () -> Unit (no parameters, no return value), and executes it like so:

    fun nonInlined(block: () -> Unit) {
        println("before")
        block()
        println("after")
    }
    

    In Java parlance, this will translate to something like this (simplified!):

    public void nonInlined(Function block) {
        System.out.println("before");
        block.invoke();
        System.out.println("after");
    }
    

    And when you call it from Kotlin...

    nonInlined {
        println("do something here")
    }
    

    Under the hood, an instance of Function will be created here, that wraps the code inside the lambda (again, this is simplified):

    nonInlined(new Function() {
        @Override
        public void invoke() {
            System.out.println("do something here");
        }
    });
    

    So basically, calling this function and passing a lambda to it will always create an instance of a Function object.


    On the other hand, if you use the inline keyword:

    inline fun inlined(block: () -> Unit) {
        println("before")
        block()
        println("after")
    }
    

    When you call it like this:

    inlined {
        println("do something here")
    }
    

    No Function instance will be created, instead, the code around the invocation of block inside the inlined function will be copied to the call site, so you'll get something like this in the bytecode:

    System.out.println("before");
    System.out.println("do something here");
    System.out.println("after");
    

    In this case, no new instances are created.

    0 讨论(0)
  • 2020-12-04 06:14

    Let me add: When not to use inline:

    1. If you have a simple function that doesn't accept other functions as an argument, it does not make sense to inline them. IntelliJ will warn you:

      Expected performance impact of inlining '...' is insignificant. Inlining works best for functions with parameters of functional types

    2. Even if you have a function "with parameters of functional types", you may encounter the compiler telling you that inlining does not work. Consider this example:

       inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
           val o = operation //compiler does not like this
           return o(param)
       }
      

      This code won't compile, yielding the error:

      Illegal usage of inline-parameter 'operation' in '...'. Add 'noinline' modifier to the parameter declaration.

      The reason is that the compiler is unable to inline this code, particularly the operation parameter. If operation is not wrapped in an object (which would be the result of applying inline), how can it be assigned to a variable at all? In this case, the compiler suggests making the argument noinline. Having an inline function with a single noinline function does not make any sense, don't do that. However, if there are multiple parameters of functional types, consider inlining some of them if required.

    So here are some suggested rules:

    • You can inline when all functional type parameters are called directly or passed to other inline function
    • You should inline when ^ is the case.
    • You cannot inline when function parameter is being assigned to a variable inside the function
    • You should consider inlining if at least one of your functional type parameters can be inlined, use noinline for the others.
    • You should not inline huge functions, think about generated byte code. It will be copied to all places the function is called from.
    • Another use case is reified type parameters, which require you to use inline. Read here.
    0 讨论(0)
  • 2020-12-04 06:15

    Higher-order functions are very helpful and they can really improve the reusability of code. However, one of the biggest concerns about using them is efficiency. Lambda expressions are compiled to classes (often anonymous classes), and object creation in Java is a heavy operation. We can still use higher-order functions in an effective way, while keeping all the benefits, by making functions inline.

    here comes the inline function into picture

    When a function is marked as inline, during code compilation the compiler will replace all the function calls with the actual body of the function. Also, lambda expressions provided as arguments are replaced with their actual body. They will not be treated as functions, but as actual code.

    In short:- Inline-->rather than being called ,they are replaced by the function's body code at compile time...

    In Kotlin, using a function as a parameter of another function (so called higher-order functions) feels more natural than in Java.

    Using lambdas has some disadvantages, though. Since they’re anonymous classes (and therefore, objects), they need memory (and might even add to the overall method count of your app). To avoid this, we can inline our methods.

    fun notInlined(getString: () -> String?) = println(getString())
    
    inline fun inlined(getString: () -> String?) = println(getString())
    

    From the above example:- These two functions do exactly the same thing - printing the result of the getString function. One is inlined and one is not.

    If you’d check the decompiled java code, you would see that the methods are completely identical. That’s because the inline keyword is an instruction to the compiler to copy the code into the call-site.

    However, if we are passing any function type to another function like below:

    //Compile time error… Illegal usage of inline function type ftOne...
     inline fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
        //passing a function type to another function
        val funOne = someFunction(ftOne)
        /*...*/
     }
    

    To solve that, we can rewrite our function as below:

    inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
        //passing a function type to another function
        val funOne = someFunction(ftOne)
        /*...*/}
    

    Suppose we have a higher order function like below:

    inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int) {
        //passing a function type to another function
        val funOne = someFunction(ftOne)
        /*...*/}
    

    Here, the compiler will tell us to not use the inline keyword when there is only one lambda parameter and we are passing it to another function. So, we can rewrite above function as below:

    fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int) {
        //passing a function type to another function
        val funOne = someFunction(ftOne)
        /*...*/
    }
    

    Note:-we had to remove the keyword noinline as well because it can be used only for inline functions!

    Suppose we have function like this -->

    fun intercept() {
        // ...
        val start = SystemClock.elapsedRealtime()
        val result = doSomethingWeWantToMeasure()
        val duration = SystemClock.elapsedRealtime() - start
        log(duration)
        // ...}
    

    This works fine but the meat of the function’s logic is polluted with measurement code making it harder for your colleagues to work what’s going on. :)

    Here’s how an inline function can help this code:

     fun intercept() {
        // ...
        val result = measure { doSomethingWeWantToMeasure() }
        // ...
        }
     }
    
     inline fun <T> measure(action: () -> T) {
       val start = SystemClock.elapsedRealtime()
       val result = action()
       val duration = SystemClock.elapsedRealtime() - start
       log(duration)
       return result
     }
    

    Now I can concentrate on reading what the intercept() function’s main intention is without skipping over lines of measurement code. We also benefit from the option of reusing that code in other places where we want to

    inline allows you to call a function with a lambda argument within a closure ({ ... }) rather than passing the lambda like measure(myLamda)

    When is this useful?

    The inline keyword is useful for functions that accept other functions, or lambdas, as arguments.

    Without the inline keyword on a function, that function's lambda argument gets converted at compile time to an instance of a Function interface with a single method called invoke(), and the code in the lambda is executed by calling invoke() on that Function instance inside the function body.

    With the inline keyword on a function, that compile time conversion never happens. Instead, the body of the inline function gets inserted at its call site and its code is executed without the overhead of creating a function instance.

    Hmmm? Example in android -->

    Let's say we have a function in an activity router class to start an activity and apply some extras

    fun startActivity(context: Context,
                  activity: Class<*>,
                  applyExtras: (intent: Intent) -> Unit) {
      val intent = Intent(context, activity)
      applyExtras(intent)
      context.startActivity(intent)
      }
    

    This function creates an intent, applies some extras by calling the applyExtras function argument, and starts the activity.

    If we look at the compiled bytecode and decompile it to Java, this looks something like:

    void startActivity(Context context,
                   Class activity,
                   Function1 applyExtras) {
      Intent intent = new Intent(context, activity);
      applyExtras.invoke(intent);
      context.startActivity(intent);
      }
    

    Let's say we call this from a click listener in an activity:

    override fun onClick(v: View) {
    router.startActivity(this, SomeActivity::class.java) { intent ->
    intent.putExtra("key1", "value1")
    intent.putExtra("key2", 5)
    }
     }
    

    The decompiled bytecode for this click listener would then look like something like this:

    @Override void onClick(View v) {
    router.startActivity(this, SomeActivity.class, new Function1() {
    @Override void invoke(Intent intent) {
      intent.putExtra("key1", "value1");
      intent.putExtra("key2", 5);
    }
     }
    }
    

    A new instance of Function1 gets created every time the click listener is triggered. This works fine, but it's not ideal!

    Now let's just add inline to our activity router method:

    inline fun startActivity(context: Context,
                         activity: Class<*>,
                         applyExtras: (intent: Intent) -> Unit) {
     val intent = Intent(context, activity)
     applyExtras(intent)
     context.startActivity(intent)
     }
    

    Without changing our click listener code at all, we're now able to avoid the creation of that Function1 instance. The Java equivalent of the click listener code would now look something like:

    @Override void onClick(View v) {
    Intent intent = new Intent(context, SomeActivity.class);
    intent.putExtra("key1", "value1");
    intent.putExtra("key2", 5);
    context.startActivity(intent);
    }
    

    Thats it.. :)

    To "inline" a function basically means to copy a function's body and paste it at the function's call site. This happens at compile time.

    0 讨论(0)
  • 2020-12-04 06:21

    One simple case where you might want one is when you create a util function that takes in a suspend block. Consider this.

    fun timer(block: () -> Unit) {
        // stuff
        block()
        //stuff
    }
    
    fun logic() { }
    
    suspend fun asyncLogic() { }
    
    fun main() {
        timer { logic() }
    
        // This is an error
        timer { asyncLogic() }
    }
    

    In this case, our timer won't accept suspend functions. To solve it, you might be tempted to make it suspend as well

    suspend fun timer(block: suspend () -> Unit) {
        // stuff
        block()
        // stuff
    }
    

    But then it can only be used from coroutines/suspend functions itself. Then you'll end up making an async version and a non-async version of these utils. The problem goes away if you make it inline.

    inline fun timer(block: () -> Unit) {
        // stuff
        block()
        // stuff
    }
    
    fun main() {
        // timer can be used from anywhere now
        timer { logic() }
    
        launch {
            timer { asyncLogic() }
        }
    }
    

    Here is a kotlin playground with the error state. Make the timer inline to solve it.

    0 讨论(0)
  • 2020-12-04 06:37

    The most important case when we use the 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 are a perfect example.

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

    If 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 the inline modifier. This is why we will have a warning on Android Studio or IDEA IntelliJ.

    Also, there is a code size problem. Inlining a large function could dramatically increase the size of the bytecode because it's copied to every call site. In such cases, you can refactor the function and extract code to regular functions.

    0 讨论(0)
提交回复
热议问题