Optimizing Kotlin Performance with Inline Functions
Inline Functions
Higher-order functions in Kotlin can incur runtime overhead due to function objects and closure capture, leading to memory allocation and virtual call costs. Inline functions mitigate this by copying the function's bytecode directly into the call site, eliminating these overheads.
For example, consider a lock function:
inline fun <T> synchronized(lockObj: Lock, operation: () -> T): T {
lockObj.lock()
try {
return operation()
} finally {
lockObj.unlock()
}
}
When called as synchronized(lock) { performTask() }, the compiler generates code equivalent to:
lock.lock()
try {
performTask()
} finally {
lock.unlock()
}
This avoids creating a function object for the lambda parameter.
Controlling Inlining with noinline
To prevent specific lambda parameters from being inlined, use the noinline modifier:
inline fun process(
inlineBlock: () -> Unit,
noinline deferredBlock: () -> Unit
) {
inlineBlock()
val storedAction = deferredBlock // Can be stored or passed
}
noinline parameters can be manipulated freely, such as stored in variables or passed to other functions.
Non-Local Returns
In regular lambda expressions, a return statement is restricted to exiting the lambda itself. However, inline lambdas support non-local returns, allowing control to exit the enclosing function:
fun findValue(values: List<Int>): Boolean {
values.forEach {
if (it == 42) return true // Exits findValue
}
return false
}
This works because the lambda is inlined into the calling context.
Crossinline Lambdas
When a lambda is executed in a different context (e.g., within a local object), non-local returns are disallowed. Mark such parameters with crossinline:
inline fun executeAsync(crossinline task: () -> Unit) {
val runnable = object : Runnable {
override fun run() = task()
}
// Execute runnable
}
This ensures the lambda does not perform non-local returns.
Reified Type Parameters
Inline functions can use reifeid type parameters to access type information at runtime without reflection:
inline fun <reified T> findParentOfType(node: TreeNode): T? {
var current = node.parent
while (current != null && current !is T) {
current = current.parent
}
return current as T?
}
Call it concisely: node.findParentOfType<SpecificNode>().
Reified types also work with reflection if needed:
inline fun <reified T> listMembers() = T::class.members
Inline Properties
From Kotlin 1.1, inline modifiers can apply to property accessors without backing fields:
val computedValue: Int
inline get() = 42
var managedState: String
get() = fetchState()
inline set(value) { updateState(value) }
Or inline the entire property:
inline var counter: Int
get() = readCount()
set(value) { writeCount(value) }
Restrictions on Public API Inline Functions
Public or protceted inline functions are considered module-level APIs. To maintain binary compatibility, their bodies cannot reference private or internal declarations unless annotated with @PublishedApi:
@PublishedApi
internal fun helper() { /* ... */ }
inline fun publicApiFunction() {
helper() // Allowed due to @PublishedApi
}
This prevents incompatibilities when modules are updated independently.