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. Вопросы на собесе
  1. Что такое inline функции в Kotlin и зачем они нужны?
  1. Как использовать crossinline и noinline в функциях?
  1. Почему reified позволяет понять тип объекта?
  1. Почему параметр reified можно использовать только с inline-функциями?
  1. Когда функцию стоит инлайнить?
  1. Когда функцию не стоит инлайнить?
  1. Минусы использования inline-функций?

    Увеличивается размер байт-кода. Возрастает сложность отладки так как код становится менее читаемым.

  1. Как понять, что функцию нужно инлайнить?

    Руководства нет. каждый определяет для себя. если функция большая и часто вызывается то имеет смысл

  1. За счет чего выполняется оптимизация в inline-функциях?

    За счет замены вызова функции непосредственно вставленным в код телом функции (сокращение количества вызовов). Это позволяет избежать накладных расходов на вызов функции и может улучшить производительность.

  1. Действие какого модификатора описано ниже? Модификатор влияет и на функцию, и на лямбду, переданную ей: они обе будут встроены вместо вызова

    noinline

    crossinline

    reified

    inline