But what exactly are inline functions, and why are they so useful? Let's dive in.
inline functions are a special kind of function designed to optimize performance, especially when dealing with higher-order functions—functions that take other functions as parameters or return them.
inline fun measureExecutionTime(block: () -> Unit) { }
When you declare a function as inline
, the compiler doesn't invoke the function normally. Instead, it inlines the function's bytecode directly into the caller's code. This means that the function's body replaces the function call at the call site.
Inline functions are primarily used for performance optimizations. Here’s how:
Avoiding Lambda Overhead: By inlining, Kotlin avoids creating an object for the lambda and directly places the lambda code in the caller’s context.
Improved Performance: Eliminates the overhead of function calls and reduces call-stack depth.
To make a function inline, you simply prefix it with the inline
keyword. Here's an example:
inline fun measureExecutionTime(block: () -> Unit) {
val startTime = System.currentTimeMillis()
block() // Call the lambda
val endTime = System.currentTimeMillis()
println("Execution time: ${endTime - startTime} ms")
}
You can use this function as follows:
fun main() {
measureExecutionTime {
for (i in 1..1_000_000) {
// Simulate computation
}
}
}
Let’s analyze the bytecode generated for an inline function versus a non-inline function.
fun nonInlineMeasure(block: () -> Unit) {
val startTime = System.currentTimeMillis()
block()
val endTime = System.currentTimeMillis()
println("Execution time: ${endTime - startTime} ms")
}
fun main() {
nonInlineMeasure {
for (i in 1..1_000_000) {
// Simulate computation
}
}
}
When you decompile the bytecode to Java using IntelliJ IDEA (Tools > Kotlin > Show Kotlin Bytecode
and then "Decompile"), you’ll see something like this:
public final class MainKt {
public static final void nonInlineMeasure(Function0<Unit> block) {
long startTime = System.currentTimeMillis();
block.invoke();
long endTime = System.currentTimeMillis();
System.out.println("Execution time: " + (endTime - startTime) + " ms");
}
public static final void main() {
MainKt.nonInlineMeasure((Function0)(new Function0() {
public final void invoke() {
for (int i = 1; i <= 1000000; i++) {
// Simulate computation
}
}
}));
}
}
Here, you can see that a lambda object is created (new Function0
) and the invoke()
method is called, adding runtime overhead.
Now, let's try inline function:
inline fun inlineMeasure(block: () -> Unit) {
val startTime = System.currentTimeMillis()
block()
val endTime = System.currentTimeMillis()
println("Execution time: ${endTime - startTime} ms")
}
fun main() {
inlineMeasure {
for (i in 1..1_000_000) {
// Simulate computation
}
}
}
When decompiled, you’ll notice the absence of a lambda object:
public final class MainKt {
public static final void main() {
long startTime = System.currentTimeMillis();
for (int i = 1; i <= 1000000; i++) {
// Simulate computation
}
long endTime = System.currentTimeMillis();
System.out.println("Execution time: " + (endTime - startTime) + " ms");
}
}
The body of the inline function (block()
) is directly inserted into the main()
method. This eliminates the need for the Function0
object and its invoke()
call.
No Lambda Object Creation: The inline example avoids creating the Function0
object.
Direct Bytecode Insertion: The lambda body is directly inserted into the calling code, improving runtime performance.
While inline functions sound great, there are scenarios where they are not suitable:
Large Functions: Inlining large functions can increase bytecode size significantly.
Recursion: Inline functions cannot be recursive.
No Real Benefit: If the function doesn't take a lambda or is rarely called, inlining provides little advantage.
noinline
and crossinline
For scenarios where you don’t want certain lambdas to be inlined or need non-local returns, Kotlin provides the noinline
and crossinline
keywords. We'll discuss these in-depth in the next blog,
Here are the key takeaways about inline functions in Kotlin:
Performance Optimization: Inline functions reduce runtime overhead by avoiding lambda object creation and function call overhead, making your code more efficient.
Simpler Bytecode: By directly inserting the function body into the calling code, inline functions eliminate additional layers of abstraction.
Use Cases Matter: Inline functions shine when used with higher-order functions in performance-critical areas. Avoid using them for large functions or where recursion is required.
Readable and Clean Code: Inline functions allow you to write concise and performant code without compromising readability.
Bytecode Awareness: Examining bytecode helps you understand the underlying optimizations and make informed decisions about when and where to use inline functions.
Also check:
Noinline in Kotlin - https://www.androidengineers.in/blogs/power-of-noinline-in-kotlin-8yp9fs
Crossline in Kotlin - https://www.androidengineers.in/blogs/crossinline-in-kotlin-c0b8i9
Akshay Nandwana
Founder AndroidEngineers
You can connect with me on:
Join our upcoming classes
https://www.androidengineers.in/courses