Inline Functions
Do you remember when you first started programming, and instead of breaking repeated code into a function, you copied and pasted it anywhere you needed? That’s what the compiler does with an inline function.
The compiler inlines the function and any lambdas passed to it, meaning it takes the function’s bytecode and pastes it directly at the call site.
When should you use an inline
function?
Performance
We use inline functions every time we use a collection operation. I’m talking about map
, filter
, etc. This is great news since it allows us to use higher order functions at no cost—enabling clean and fast functional programming.
Why does inlining gain performance?
To understand why inlining functions can increase performance, we have to understand what Kotlin does when we don’t inline a function:
- It jumps to a different place in memory, to get the function’s code.
- If the function contains a lambda argument, an extra object is created to hold all of its information.
Therefore when we inline a function, we avoid a memory jump, and if there’s a lambda argument, we avoid extra object creation. In general this is not game changing, but for functions like map
, forEach
, and filter
, which are heavily used and take lambda function arguments, inlining is a valuable feature.
Non-local returns
Speaking of the collection operations: have you noticed you can return from them?
Try that from a normal function, the compiler won’t allow it.
In the first example, because the compiler is inlining the code, in terms of return
, it’s as if the function call wasn’t there. return
goes back to the nearest fun
declaration, and with inline functions, the compiler has stripped that away.
Reified Type Parameters
Sometimes, functions using generics need type information. For example, the collection function filterIsInstance
.
Because the function is inlined, we don’t have to do any reflection. We can use the type operators is
and as
. The filterIsInstance
function is a simple extension function on Iterable
that does just that.
Why doesn’t type erasure apply to inlined functions?
Because the compiler is inlining the function’s bytecode, it knows the actual type used as an argument. When the compiler pastes the code, there is no notion of generics. It’s using a real type—the type is used at that particular call site. A type we can access and get information about.
This is significantly different behaviour than a normal function. It’s so different that it allows us to gather type information, even in the presence of type erasure.
Limitations of inline
functions
You might ask—why not make every function inline?
It’s easy to abuse inline functions. If we create a large inline function, or an inline function that calls an inline function, our code can quickly balloon in size. When in doubt, think about what the compiler is doing–are you comfortable with it pasting the code at every call site? Are you content losing the stack-trace if you need to debug? If not, use a regular function.
Furthermore, inline functions cannot use elements with a more restrictive visibility [4]. Your inline function might be pasted to a class in another module, where that private class it’s using won’t be accessible.
Lastly, inline functions can’t be recursive. Think about what the compiler would do if it encountered one.
Inline functions are a simple tool to increase performance, but they should only be used at the right time.
Sources
Wow! You read the whole thing. People who make it this far sometimes
want to receive emails when I post something new.
I also have an RSS feed.