Inline Functions
https://kotlinlang.org/docs/inline-functions.html |
Inline Functions
Способ оптимизации производительности за счет устранения накладных расходов, связанных с вызовом функций, особенно при использовании лямбд. Основная идея состоит в том, что тело функции и переданные лямбды будут вставлены (“инлайнятся”) в место вызова функции, что исключает создание дополнительных объектов и вызовов. В Kotlin лямбды активно используются, но их вызов обычно создает накладные расходы на создание дополнительных объектов (анонимных классов или функций), что может сказаться на производительности в критичных участках кода (например, в циклах или высоконагруженных операциях). Инлайн-функции помогают решить эту проблему.
Когда инлайнить функцию?
• Использование лямбд как аргументов. Если функция принимает лямбды в качестве аргументов, инлайнинг может значительно повысить производительность. Без инлайнинга для каждой лямбды будет создаваться объект, что требует дополнительной памяти и времени на его создание. Инлайн-функция вставляет тело лямбды напрямую в место вызова.
// В этом случае лямбда action не требует создания отдельного объекта при вызове performOperation().
inline fun performOperation(action: () -> Unit) {
action()
}
• Избегание накладных расходов на вызов функции. Если функция вызывается очень часто или выполняется в цикле, накладные расходы на вызов функции могут стать значительными. Инлайнинг может помочь избавиться от этих затрат, так как вместо вызова функции её код просто подставляется на место вызова.
// В цикле или при частом вызове логирования инлайнинг устранит накладные расходы на вызов функции.
inline fun logMessage(message: String) {
println(message)
}
• Использование reified типов. Как уже упоминалось, ключевое слово reified работает только с inline функциями. Если функция должна манипулировать типом generics на этапе выполнения, её нужно инлайнить.
// Без инлайнинга нельзя будет сохранить информацию о типе T для проверки на этапе выполнения.
inline fun <reified T> isType(value: Any): Boolean {
return value is T
}
• Код маленького объёма. Если функция имеет небольшое тело, инлайнинг может быть выгоден, так как вставка тела функции не приведёт к существенному увеличению объёма сгенерированного байт-кода, но избавит от накладных расходов на вызов.
inline fun add(a: Int, b: Int): Int = a + b
Когда не стоит инлайнить?
• Крупные функции: Если функция большая, инлайнинг может привести к значительному увеличению объёма байт-кода, что замедлит компиляцию и увеличит размер программы.
• Рекурсивные функции: Рекурсивные функции не могут быть инлайн-функциями, так как это приведёт к бесконечной подстановке вызовов самой функции в себя.
• Редко вызываемые функции: Если функция вызывается редко, инлайнинг может быть излишним.
inline
Сделать функцию встроенной.
inline fun example(block: () -> Unit) {
block()
}
noinline
Не встраивать функцию переданную в аргумент.
inline fun inlineExample(inlined: () -> Unit, noinline notInlined: () -> Unit) {
inlined() // будет инлайнятся
notInlined() // не будет инлайнятся
}
crossinline
Запретить вызов return
в лямбде.
inline fun crossinlineExample(crossinline block: () -> Unit) {
Thread {
block() // не позволит использовать return для выхода из всей функции
}.start()
}
reified
Используется в сочетании с inline-функциями, чтобы позволить работать с типами generics на этапе выполнения (runtime). В обычных generic-функциях информация о типах стирается во время компиляции из-за механизма type erasure в JVM, но с помощью reified можно сохранить тип и оперировать им в коде. Благодаря встраиванию, компилятор может “развернуть” функцию и получить конкретный тип, переданный в качестве аргумента, и использовать его в коде. Это делает возможным использование таких операций, как проверка типа is
или создание объектов newInstance
, которые требуют информации о типах во время выполнения.
inline fun <reified T> checkType(value: Any) {
if (value is T) {
println("This is of type ${T::class}")
} else {
println("This is not of type ${T::class}")
}
}
Inline Properties
Представляют собой механизм, позволяющий инлайнить геттеры и сеттеры свойств, чтобы избежать накладных расходов, связанных с вызовом методов. Это работает аналогично инлайн-функциям, и применяется в случаях, когда геттер или сеттер выполняет простые операции, такие как возвращение значения или присвоение.
val password: String
inline get() = "qwerty"
var complexProperty: Int
inline get() {
val x = 2
val y = 4
return x + y
}
inline set(value) {
val adjustedValue = value + 10
println("Setting adjusted value $adjustedValue")
}
Kotlin Inline Functions. Вопросы на собесе
- Что такое inline функции в Kotlin и зачем они нужны?
- Как использовать crossinline и noinline в функциях?
- Почему reified позволяет понять тип объекта?
- Почему параметр reified можно использовать только с inline-функциями?
- Когда функцию стоит инлайнить?
- Когда функцию не стоит инлайнить?
- Минусы использования inline-функций?
Увеличивается размер байт-кода. Возрастает сложность отладки так как код становится менее читаемым.
- Как понять, что функцию нужно инлайнить?
Руководства нет. каждый определяет для себя. если функция большая и часто вызывается то имеет смысл
- За счет чего выполняется оптимизация в inline-функциях?
За счет замены вызова функции непосредственно вставленным в код телом функции (сокращение количества вызовов). Это позволяет избежать накладных расходов на вызов функции и может улучшить производительность.
- Действие какого модификатора описано ниже? Модификатор влияет и на функцию, и на лямбду, переданную ей: они обе будут встроены вместо вызова
• noinline
• crossinline
• reified
• inline