Inline Functions in Kotlin

But what exactly are inline functions, and why are they so useful? Let's dive in.

What Are Inline Functions?

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.

Why Use Inline Functions?

Inline functions are primarily used for performance optimizations. Here’s how:

  1. Avoiding Lambda Overhead: By inlining, Kotlin avoids creating an object for the lambda and directly places the lambda code in the caller’s context.

  2. Improved Performance: Eliminates the overhead of function calls and reduces call-stack depth.

How to Define an Inline Function?

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
        }
    }
}

Bytecode Comparison: Inline vs Non-Inline

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.

Benefits of Inline Functions in Action

  1. No Lambda Object Creation: The inline example avoids creating the Function0 object.

  2. Direct Bytecode Insertion: The lambda body is directly inserted into the calling code, improving runtime performance.

When Not to Use Inline Functions

While inline functions sound great, there are scenarios where they are not suitable:

  1. Large Functions: Inlining large functions can increase bytecode size significantly.

  2. Recursion: Inline functions cannot be recursive.

  3. No Real Benefit: If the function doesn't take a lambda or is rarely called, inlining provides little advantage.

Exploring 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,

Conclusion

Here are the key takeaways about inline functions in Kotlin:

  1. Performance Optimization: Inline functions reduce runtime overhead by avoiding lambda object creation and function call overhead, making your code more efficient.

  2. Simpler Bytecode: By directly inserting the function body into the calling code, inline functions eliminate additional layers of abstraction.

  3. 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.

  4. Readable and Clean Code: Inline functions allow you to write concise and performant code without compromising readability.

  5. 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:


Book 1:1 Session here
Click Here

Join our upcoming classes
https://www.androidengineers.in/courses

Love from our past students

Excellent list of questions really helped me to coverup all the topics before interview.

Saiteja Janjirala

10th Oct, 2024

I had an exceptional experience with the 1:1 mentorship session. Akshay was incredibly friendly and provided invaluable guidance on focusing on long-term goals. They also gave great interview tips, including a thorough resume review. Additionally, the discussion on Data Structures and Algorithms (DSA) was insightful and practical. Highly recommended for anyone looking to advance their career!

Nayab khan

11th Sep, 2024

Cleared my major points for what I am missing in the resume and also suggested what I can work on for further growth in the career.

Ketan Chaurasiya

7th Aug, 2024

What impressed me most was his personalized approach and practical tips that I could immediately apply. Akshay’s guidance not only improved my technical skills but also boosted my confidence in navigating my career path. His insights and encouragement have been a game-changer for me. I highly recommend Akshay’s mentorship to anyone looking to advance their Android development career.

Hardik Kubavat

5th Aug, 2024

2025© Made with   by Android Engineers.