Why inline functions

  • Because: Kotlin introduced lambda for simplicity of writing.

  • But: Lambdas can be a performance drain.

  • So: an inline function is introduced to solve this problem.

How to prove lambda is easy to write

Let’s implement a requirement, diff, for lambda and lambda free code.

Requirement: Implement a function callback, callback a String to me.

Java version (without lambda):

Public interface Action {void click(String fuck); Public void func(Action Action) {String STR = "hello"; action.click(str); } public static void main(String[] args) {// We need to create an anonymous class func(new Action() {@override public void click(String fuck) { System.out.println(fuck); }}); }Copy the code

Then we have kotlin’s version:

Func (action: (String) -> Unit) {val STR = "hello" action.invoke(STR)} It is the default argument to lambda func {println(it)}}Copy the code

Without contrast, there is no harm. Java took a lot of effort to write several lines, and Kotlin implemented them in just a few lines, which is the virtue of Lambda: simplicity and ease of use. In plain English: no object creation.

Although the readability is a little bit poor, regardless of it, it is also other people’s business not to understand, others can not understand to highlight my irreplaceable.

Lambda, it turns out, makes writing code so much easier that we don’t have to type the code that creates the object.

So, what’s the downside of lambda?

The disadvantage of lambda

The biggest drawback of lambda is performance loss!

Let’s decompile the kotlin code above:

// This parameter has been replaced with Function1, Public static final void func(@notnull Function1 action) {Function1 is an interface defined in Kotlin. Intrinsics.checkNotNullParameter(action, "action"); String str = "hello"; action.invoke(str); } public static final void main() {func((Function1) null.instance); }Copy the code

As we can see, the lambda in Kotlin will eventually become an anonymous class at compile time, which is not much different from Java, which generates an anonymous class. Kotlin’s lambda is inefficient because: Kotlin creates anonymous classes at compile time.

In Java, the Invokedynamic directive was introduced after 1.7. Lambda in Java is replaced by invokeDynamic at compile time, and at run time, if invokeDynamic is called, an anonymous class is generated to replace the directive. Subsequent calls are made using this anonymous class.

To put it bluntly, with Java, if lambda is not called, anonymous classes are not created. In Kotlin’s case, an anonymous class is created ahead of time regardless of whether the lambda is called or not. This is equivalent to Java putting the creation of anonymous classes behind and only doing it when needed, which saves overhead. Because creating anonymous classes increases the number of classes and the bytecode size.

So, why doesn’t Kotlin do the same, why does he have to do something ahead of time that he won’t necessarily use in the future? Because kotlin needs to be compatible with java6, which is currently the main development language for Android, invokedynamic was introduced after java7… , MMP!

So, how does Kotlin clean this ass? Use inline functions!

The implementation principle of inline functions

With the same code, we changed func to inline as follows:

Fun main() {func {print(it)}} // Uses inline to modify inline fun func(action: (String) -> Unit) { val str = "hello" action.invoke(str) }Copy the code

Again, let’s decompile:

/ / this function is not changing the public static final void func (@ NotNull once Function1 action) {Intrinsics. CheckNotNullParameter (action, "action"); String str = "hello"; action.invoke(str); } six six six public static final void main() {String STR $iv = "hello"; System.out.print(str$iv); }Copy the code

As we can see, after inline is added, Kotlin copies the body of the called function directly to the place where it was called.

So you don’t have to create anonymous objects! Also, there is one less call to the procedure. Because the function that calls an anonymous object is itself one more call. Such as:

Public void test(){A A = new A (); a.hello(); Public void test(){// a.hello() {// a.hello(); }Copy the code

So, the inline cow, hooray hooray hooray.

However, there are downsides to inlining! For example, I now have an inline function test() that has 1000 lines of code in it, and if it is called in 10 places, it will be copied to those 10 places, making 10,000 lines… This causes the class file to grow in size, which in turn causes the APK to grow so large that users don’t want to see it.

What do you do? Don’t inline! In other words, depending on the size of the function and how many times it is called, you need to inline it or not.

This is a business decision, no more nonsense here.

Other rules for inline functions

Ok, so let’s look at some rules for inline functions.

Limitations of inline functions

An inline function as an argument can only be passed to another inline function. Such as:

// func2 non-inline fun func2(action: (String) -> Unit) {} // func is inline fun func(action: (String) -> Unit) {val STR = "hello" action.invoke(STR) // Func2 (action) // func2(action) // func2(action) //Copy the code

Now let’s say func2 is changed to inline:

Func2 (action: (String) -> Unit) {} (String) -> Unit) {val STR = "hello" action.invoke(STR) // Pass an action to another inline function func2, func2(action) // ok}Copy the code

What if, instead of changing func2() to inline, you can use noinline to modify the action argument:

// func2 non-inline fun func2(Action: (String) -> Unit) {} // func is inline, but the action is marked as non-inline inline fun func(noinline action: (String) -> Unit) {val STR = "hello" action.invoke(STR) // Func2 func2(action) // ok}Copy the code

Nonlocal return of an inline function citation

Local return

As we know, the return of a normal function call is local, for example:

// return, Fun tReturn() {return} fun func() {println("before") // Calls toRetrun() tReturn() println("after")} // tests  fun main() { func() }Copy the code

The results are as follows:

before
after
Copy the code

This is normal because the func() function prints before, then calls tReturn(), tReturn() pushes onto the stack, returns, tReturn() exits the stack, returns to the func() function, and then prints down after.

However, if func() is declared inline and tReturn() is passed as an argument, the body of the func() method changes, as in:

// Declare func inline, then pass in the action parameter inline fun func(action: () -> Unit) {println("before") action.invoke() println("after")} fun main() {// Func {return}}Copy the code

Results:

before
Copy the code

The principle is simple, because the action argument is copied to the func() function and merged into a method, equivalent to:

Println ("after")} inline fun func() {println("before") return inline fun func() {println("after")}Copy the code

Instead of inline, just change the parameter to action, for example:

// This is action fun func(action: () -> Unit) {println("before") action.invoke() println("after")} fun main() {func {return}Copy the code

This will result in an error:

Kotlin: 'return' is not allowed here
Copy the code

This is not allowed because it doesn’t know where you want to return to, but it can be written like this:

Func {return@func}} func {return@func}}Copy the code

Results:

before
after
Copy the code

To sum up, a normal function argument returns locally, whereas an inline function returns globally.

How do you protect against this risk, or how do you make a function inline without having a global return for its arguments? Such as:

Inline fun func(action: () -> Unit) {println("before") action()} inline fun func(action: () -> Unit) {println("before") action()}Copy the code

Use Crossinline! We modify the function as follows:

Crossinline inline fun func(crossinline action: () -> Unit) {println("before") action() println("after")} 'return' is not allowed here func {return@func}Copy the code

As you can see, corssinline limits the global return while ensuring that the function is inline

conclusion

  • Kotlin introduced lambda for brevity
  • But lambda has a performance overhead
  • The performance overhead is optimized in java7, but kotlin is java6-compatible and does not enjoy this optimization
  • So Kotlin introduced inline to solve this problem
  • Inlining is copying the called function into the caller’s function body at compile time, avoiding the creation of inner classes
  • Using inline, functions can be declared inline, and inline function arguments are returned globally
  • Use noinline to modify function arguments to be inlined
  • Crossinline allows you to modify function arguments to be inline and not returned globally