This article is simultaneously published on my wechat official account. You can follow it by searching OpenCV or Android on wechat.
concept
Inline function: In computer science, an inline function (sometimes called an inline function or compile-time expansion function) is a programming language construct used to suggest that the compiler extend inline (sometimes called an inline extension) for a particular function; That is, it is recommended that the compiler insert the specified function body instead of the context where the function is called, thus saving extra time for each call. However, when choosing to use inline functions, there must be a trade-off between the space occupied by the program and the efficiency of the program execution, because too many more complex functions for inline extension will incur a large storage resource expenditure. It is also important to note that inline extensions to recursive functions can cause infinite compilation of partial compilers.
In short, the inline function is directly expanded where it is called, and the compiler does not need to push the parameter stack, push the parameter out of the stack and release the resource when it is called like a general function, so as to improve the efficiency of the program.
inline
Higher-order functions are one of the most popular features of the Kotlin language. What are higher-order functions? Higher-order functions are those that use functions as arguments or return values. However, there is some run-time efficiency penalty when using higher-order functions, because each function is an object. Here are some examples:
class InlineMain {
companion object {
@JvmStatic
fun main(args: Array<String>) {
greeting {
println("After")}}private fun greeting(after: () -> Unit) {
println("Hello")
after()
}
}
}
Copy the code
Use the Android Studio bytecode viewer “Decompile” to get an overview of the logic in JAVA code:
public final void main(@NotNull String[] args) {
Intrinsics.checkNotNullParameter(args, "args");
// You can see that an object is created and then the invoke method is called to run the method we passed in
((InlineMain.Companion)this).greeting((Function0)null.INSTANCE);
}
private final void greeting(Function0 after) {
String var2 = "Hello";
boolean var3 = false;
System.out.println(var2);
after.invoke();
}
Copy the code
Note: The JAVA code decomcompiled by the Android Studio Kotlin bytecode viewer is for reference only. The JVM bytecode generated by Kotlin is not equal to the JVM bytecode generated by JAVA.
As you can see from the example, each call to the greeting method creates a temporary object to call the method we passed in because of the function type parameter. If it’s called once or twice it doesn’t matter, but in high-frequency usage scenarios the method could be called 100, 1,000, or even 10,000 times, resulting in thousands of temporary objects being created. This increases the elapsed time in both memory allocation and virtual machine calls. To deal with the extra overhead of higher-order functions, Kotlin added the inline keyword.
After adding the inline modifier to greeting, the JAVA code decomcompiled from the bytecode is as follows:
public final void main(@NotNull String[] args) {
Intrinsics.checkNotNullParameter(args, "args");
InlineMain.Companion this_$iv = (InlineMain.Companion)this;
int $i$f$greeting = false;
String var4 = "Hello";
boolean var5 = false;
System.out.println(var4);
int var6 = false;
String var7 = "After";
boolean var8 = false;
System.out.println(var7);
}
Copy the code
As you can see, not only is the greeting function inlined in the main function, but the function type parameters are also inlined. **Kotlin folds several layers of code that the compiler flattens out. Through the way of inlining, you can solve the potential performance problems caused by higher-order functions. ** The folding division at the code level is to better display the business logic, while the even expansion at the compiler level is to run the program faster.
Summary: The inline keyword is recommended for frequently used high-order functions with function type parameters.
noinline
Inline applies to functions, and noinline applies to function type parameters. Inline means that the entire function is inlined, and noinline means that the function type parameter is not inlined.
Suppose an inline function takes two function type arguments:
class InlineMain {
companion object {
@JvmStatic
fun main(args: Array<String>) {
greeting({
println("Before")
}, {
println("After")})}private inline fun greeting(before: () -> Unit, after: () -> Unit) {
before()
println("Hello")
after()
}
}
}
Copy the code
The JAVA code after decomcompiling bytecode is as follows:
public final void main(@NotNull String[] args) {
Intrinsics.checkNotNullParameter(args, "args");
InlineMain.Companion this_$iv = (InlineMain.Companion)this;
int $i$f$greeting = false;
int var4 = false;
String var5 = "Before";
boolean var6 = false;
System.out.println(var5);
String var9 = "Hello";
boolean var10 = false;
System.out.println(var9);
var6 = false;
String var7 = "After";
boolean var8 = false;
System.out.println(var7);
}
Copy the code
As expected, both function type parameters are inlined by default. However, in some cases, we do not want all function type arguments to be inline. ** To solve this problem, Kotlin added the noinline keyword. ** But some of these scenarios, what are they?
- When inline modifiers are not used: function type arguments can be used as objects, which can be assigned to variables, passed as arguments to other functions, or passed as return values;
- With inline modifiers: the code is smoothed out and the temporary object does not exist, so assigning a value to a variable, passing it as an argument to another function, or passing it as a return value cannot be implemented.
In the greeting function, we pass the after() method to another function, and Android Studio prompts us to add noinline to after:
We added noinline to After, and decomcompiled JAVA code looks like this:
public final void main(@NotNull String[] args) {
Intrinsics.checkNotNullParameter(args, "args");
InlineMain.Companion this_$iv = (InlineMain.Companion)this;
// Create an object for the after function type argument
Function0 after$iv = (Function0)null.INSTANCE;
int $i$f$greeting = false;
int var5 = false;
String var6 = "Before";
boolean var7 = false;
System.out.println(var6);
String var10 = "Hello";
boolean var11 = false;
System.out.println(var10);
access$wrapAfter(this_$iv, after$iv);
String var8 = "Finish Greeting";
boolean var9 = false;
System.out.println(var8);
}
private final void greeting(Function0 before, Function0 after) {
int $i$f$greeting = 0;
before.invoke();
String var4 = "Hello";
boolean var5 = false;
System.out.println(var4);
access$wrapAfter((InlineMain.Companion)this, after);
}
private final void wrapAfter(Function0 after) {
String var2 = "Before After";
boolean var3 = false;
System.out.println(var2);
after.invoke();
}
Copy the code
You can see that before function type parameters are inlined, but after is not.
Summary: When using inline for function inlining, use Noinline for local close function inlining. More generally, we use the noinline modifier when we need to manipulate function-type parameters as objects within a function. Android Studio also gives us friendly syntax alerts.
crossinline
Whereas noinline solves the problem that a function type argument in an inline function cannot be operated on as an object, Crossinline solves the problem that a function type argument in an inline function cannot be called indirectly or returned nonlocally.
Function type arguments are called indirectly
We call after indirectly in the greeting function. Android Studio will prompt us to add crossinline to After:
After we add Crossinline to after, the JAVA code decomcompiled from the bytecode is as follows:
public final class InlineMain$Companion$main$$inlined$greetingThe $1extends Lambda implements Function0 {
public InlineMain$Companion$main$$inlined$greeting$1() {
super(0);
}
// $FF: synthetic method
// $FF: bridge method
public Object invoke(a) {
this.invoke();
return Unit.INSTANCE;
}
public final void invoke(a) {
int var1 = false;
String var2 = "After";
boolean var3 = false; System.out.println(var2); }}...public final void main(@NotNull String[] args) {
Intrinsics.checkNotNullParameter(args, "args");
InlineMain.Companion this_$iv = (InlineMain.Companion)this;
int $i$f$greeting = false;
int var4 = false;
String var5 = "Before";
boolean var6 = false;
System.out.println(var5);
String var8 = "Hello";
boolean var9 = false;
System.out.println(var8);
access$wrapAfter(this_$iv, (Function0)(new InlineMain$Companion$main$$inlined$greeting$1()));
String var7 = "Finish Greeting";
$i$f$greeting = false;
System.out.println(var7);
}
private final void greeting(Function0 before, final Function0 after) {
int $i$f$greeting = 0;
before.invoke();
String var4 = "Hello";
boolean var5 = false;
System.out.println(var4);
access$wrapAfter((InlineMain.Companion)this, (Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(a) {
this.invoke();
return Unit.INSTANCE;
}
public final void invoke(a) { after.invoke(); }})); }private final void wrapAfter(Function0 after) {
String var2 = "Before After";
boolean var3 = false;
System.out.println(var2);
after.invoke();
}
Copy the code
Function type arguments in inline functions are not allowed to be called indirectly. Crossinline modifiers allow function type arguments to be called indirectly.
Nonlocal return
In Kotlin, a return is not allowed in a Lambda expression unless the entire Lambda expression is an argument to an inline function.
We add a return statement to the end of the after function type argument as follows:
class InlineMain {
companion object {
@JvmStatic
fun main(args: Array<String>) {
greeting({
println("Before")
}, {
println("After")
return
})
println("Finish Greeting")}private inline fun greeting(before: () -> Unit, after: () -> Unit) {
before()
println("Hello")
after()
}
}
}
Copy the code
Due to function inlining optimization, both function type parameters are inlined. After the code is flat, return terminates main, causing println(“Finish Greeting”) not to execute. Similarly, if we add return to the end of before function type parameter expression, The program will end earlier. In short, for Lambda expressions:
- Normal Lambda expressions are not allowed
return
Statement, Android Studio will directly compile the error. - Lambda expressions that are arguments to inline functions are allowed
return
Statement, but ends not the immediate outer function, but the outer function after the outer function. Lambda expressions can also be used, of coursereturn@label
To display the specified return location.
However, as in the example above, if we add a return statement to the Lambda expression corresponding to the function type parameter with the Crossinline modifier, Android Studio will directly indicate that return is not allowed here. When a Lambda expression is called indirectly as an argument to an inline function, if a return can be used in the Lambda expression, the return will not behave as expected, confusing the entire code logic. So either use return@label to explicitly tell it where to return to, or disable return.