问题
I know that an inline function will maybe improve performance & cause the generated code to grow, but I'm not sure when it is correctly to use it.
lock(l) { foo() }
Instead of creating a function object for the parameter and generating a call, the compiler could emit the following code. (Source)
l.lock()
try {
foo()
}
finally {
l.unlock()
}
but I found that there is no function object created by kotlin for a non-inline function. why?
/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
lock.lock();
try {
block();
} finally {
lock.unlock();
}
}
回答1:
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.
回答2:
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 with 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. If operation
is not wrapped in an object (which is implied by inline
since you want to avoid this), 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 useinline
. Read here.
回答3:
The most important case when we use inline modifier is when we define util-like functions with parameter functions. Collection or string processing (like filter
, ma
p or joinToString
) or just standalone functions are perfect example.
This is why inline modifier is mostly an important optimization for library developers. They should know how does it work and what are its improvements and costs. We will use inline modifier in our projects when we define our own util functions with function type parameters.
When 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 inline modifier. This is why we will have a warning on Android Studio or IDEA IntelliJ.
Also there is the code size problem. Inlining a large function could dramatically increase the size of the bytecode because it's copied to every calls site. In such cases, you can refactor the function and extract code to regular functions.
回答4:
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.
来源:https://stackoverflow.com/questions/44471284/when-to-use-an-inline-function-in-kotlin