Когда использовать встроенную функцию в Котлин?

Я знаю, что встроенная функция, возможно, улучшит производительность и заставит сгенерированный код расти, но я не уверен, когда это правильно использовать.

lock(l) { foo() }

Вместо создания объекта функции для параметра и создания вызова компилятор может испустить следующий код. (Источник)

l.lock()
try {
  foo()
}
finally {
  l.unlock()
}

но я обнаружил, что функциональный объект, созданный kotlin для не-встроенной функции, отсутствует. почему?

/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
    lock.lock();
    try {
        block();
    } finally {
        lock.unlock();
    }
}

Ответ 1

Предположим, вы создаете функцию более высокого порядка, которая принимает лямбда типа () -> Unit (без параметров, никакого возвращаемого значения) и выполняет ее так:

fun nonInlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

В языке Java это будет означать нечто подобное (упрощенное!):

public void nonInlined(Function block) {
    System.out.println("before");
    block.invoke();
    System.out.println("after");
}

И когда вы называете это от Котлина...

nonInlined {
    println("do something here")
}

Под капотом здесь будет создан экземпляр Function, который обертывает код внутри лямбда (опять же, это упрощается):

nonInlined(new Function() {
    @Override
    public void invoke() {
        System.out.println("do something here");
    }
});

Таким образом, в принципе, вызов этой функции и передача лямбда к ней всегда создадут экземпляр объекта Function.


С другой стороны, если вы используете ключевое слово inline:

inline fun inlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

Когда вы вызываете это следующим образом:

inlined {
    println("do something here")
}

Нет экземпляра Function, вместо этого код, вызывающий вызов block внутри встроенной функции, будет скопирован на сайт вызова, поэтому вы получите что-то вроде этого в байт-коде:

System.out.println("before");
System.out.println("do something here");
System.out.println("after");

В этом случае новые экземпляры не создаются.

Ответ 2

Позвольте мне добавить: "Когда не использовать inline":

1) Если у вас есть простая функция, которая не принимает другие функции в качестве аргумента, вставлять их не имеет смысла. IntelliJ предупредит вас:

Ожидаемое влияние на производительность при вставке "..." незначительно. Встраивание лучше всего подходит для функций с параметрами функциональных типов

2) Даже если у вас есть функция "с параметрами функциональных типов", вы можете встретить компилятор, который скажет вам, что вставка не работает. Рассмотрим этот пример:

inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
    val o = operation //compiler does not like this
    return o(param)
}

Этот код не скомпилируется с ошибкой:

Недопустимое использование встроенного параметра 'operation' в '...'. Добавьте модификатор noinline к объявлению параметра.

Причина в том, что компилятор не может встроить этот код. Если operation не обернут в объект (что подразумевается в inline, поскольку вы хотите этого избежать), как его вообще можно назначить переменной? В этом случае компилятор предлагает сделать аргумент noinline. Наличие функции inline с одной функцией noinline не имеет никакого смысла, не делайте этого. Однако, если имеется несколько параметров функциональных типов, при необходимости рассмотрите возможность включения некоторых из них.

Итак, вот некоторые предлагаемые правила:

  • вы можете встроить, когда все параметры функционального типа вызываются напрямую или передаются другой встроенной функции
  • Вы должны встраивать, когда ^ имеет место.
  • Вы не можете встроить, когда параметр функции назначается переменной внутри функции
  • Вы должны рассмотреть возможность встраивания, если хотя бы один из параметров вашего функционального типа может быть встроен, используйте noinline для других.
  • Вы не должны вставлять огромные функции, думать о сгенерированном байт-коде. Он будет скопирован во все места, откуда вызывается функция.
  • Другой вариант использования - это параметры типа reified, которые требуют использования inline. Читайте здесь.

Ответ 3

Наиболее важный случай, когда мы используем встроенный модификатор, - это когда мы определяем подобные утилитам функции с функциями параметров. joinToString обработка коллекций или строк (например, filter, ma p или joinToString) или просто автономные функции.

Вот почему встроенный модификатор является в основном важной оптимизацией для разработчиков библиотек. Они должны знать, как это работает, каковы его улучшения и затраты. Мы будем использовать встроенный модификатор в наших проектах, когда будем определять наши собственные функции util с параметрами типа функции.

Когда у нас нет параметра типа функции, параметра reified типа, и нам не требуется нелокальный возврат, тогда мы, скорее всего, не должны использовать встроенный модификатор. Вот почему у нас будет предупреждение о Android Studio или IDEA IntelliJ.

Также есть проблема с размером кода. Добавление большой функции может значительно увеличить размер байт-кода, поскольку он копируется на каждый сайт вызовов. В таких случаях вы можете реорганизовать функцию и извлечь код в обычные функции.

Ответ 4

Один простой случай, когда вам может понадобиться один, - это когда вы создаете функцию util, которая принимает блок приостановки. Учтите это.

fun timer(block: () -> Unit) {
    // stuff
    block()
    //stuff
}

fun logic() { }

suspend fun asyncLogic() { }

fun main() {
    timer { logic() }

    // This is an error
    timer { asyncLogic() }
}

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

suspend fun timer(block: suspend () -> Unit) {
    // stuff
    block()
    // stuff
}

Но тогда его можно использовать только из функций сопрограммы/приостановки. Затем вы получите асинхронную версию и не асинхронную версию этих утилит. Проблема исчезнет, если вы сделаете это встроенным.

inline fun timer(block: () -> Unit) {
    // stuff
    block()
    // stuff
}

fun main() {
    // timer can be used from anywhere now
    timer { logic() }

    launch {
        timer { asyncLogic() }
    }
}

Вот игровая площадка kotlin с ошибкой. Сделайте таймер встроенным, чтобы решить его.